From 71a300f8040d9a192c5b5d03ea226f7139e9533d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Mikel?= Date: Sat, 25 Apr 2026 20:08:38 +0200 Subject: [PATCH 01/24] Add API for IA summaries of amendments and related configurations --- .claude/settings.json | 28 ++ .claude/settings.local.json | 17 ++ .dockerignore | 3 + .../controllers/api/Amendements_ia.php | 73 +++++ .../views/dashboard-mp/amendements/index.php | 275 ++++++++++++++++++ scripts | 2 +- sql/amendements_ia.sql | 9 + view | 1 + 8 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 .claude/settings.json create mode 100644 .claude/settings.local.json create mode 100644 .dockerignore create mode 100644 application/controllers/api/Amendements_ia.php create mode 100644 application/views/dashboard-mp/amendements/index.php create mode 100644 sql/amendements_ia.sql create mode 100644 view diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..fc11b6fa2 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,28 @@ +{ + "permissions": { + "allow": [ + "Bash(grep -r \"setcookie\\\\|$this->input->set_cookie\\\\|$this->load->library\\(''session''\\)\" /c/Users/Remi/workspace/datan/application/controllers --include=\"*.php\")", + "Bash(grep -n \"$this->session\" /c/Users/Remi/workspace/datan/application/views/templates/*.php)", + "Bash(grep -n \"setcookie\\\\|set_cookie\" /c/Users/Remi/workspace/datan/application/views/templates/*.php)", + "Bash(grep -n \"setcookie\\\\|set_cookie\\\\|$this->session\" /c/Users/Remi/workspace/datan/application/hooks/*.php)", + "Bash(grep -l \"extends CI_Controller\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", + "Bash(for controller:*)", + "Bash(do echo:*)", + "Read(//c/Users/Remi/workspace/datan/application/controllers/**)", + "Bash(done)", + "Bash(grep -r \"$this->load->library\\(''session''\\)\" /c/Users/Remi/workspace/datan/application/controllers/ --include=\"*.php\")", + "Bash(grep -v \"isset\\($this->session\\)\")", + "Bash(grep -v \"^\\\\s*//.*$this->session\")", + "Bash(grep -n \"isset\\($this->session\\)\" /c/Users/Remi/workspace/datan/application/views/templates/footer.php)", + "Bash(grep -n \"$this->session\" /c/Users/Remi/workspace/datan/application/views/templates/footer.php)", + "Bash(grep -n \"$this->show_popup\" /c/Users/Remi/workspace/datan/application/views/templates/footer.php)", + "Bash(grep -r \"quizBanner\\\\|newsletter.*index\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", + "Bash(grep \"extends CI_Controller\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", + "Bash(xargs -I {} basename {})", + "Bash(find /c/Users/Remi/workspace/datan/application/libraries -type f -name *.php)", + "Bash(grep -r \"CI_Controller\\\\|Auth_Controller\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", + "Bash(grep -i \"\\\\.php$\")", + "Bash(grep -rn \"$_LES\\\\|$this->upload\\\\|do_upload\" /c/Users/Remi/workspace/datan/application/controllers/)" + ] + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..f014ae70c --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,17 @@ +{ + "permissions": { + "allow": [ + "Bash(wmic process:*)", + "Bash(powershell -Command:*)", + "Bash(/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command \"Get-Process -Id 7748 | Select-Object Name,Id,Path\")", + "Bash(net stop:*)", + "Bash(net start:*)", + "Bash(git mv:*)", + "Bash(docker info:*)", + "Bash(docker exec:*)", + "Bash(docker stats:*)", + "Bash(grep -r \"foreach\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", + "Bash(grep -r \"WHERE.*OR\\\\|UNION\\\\|subquery\\\\|IN \\(\" /c/Users/Remi/workspace/datan/application/models/*.php)" + ] + } +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..b4eee3758 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +.claude diff --git a/application/controllers/api/Amendements_ia.php b/application/controllers/api/Amendements_ia.php new file mode 100644 index 000000000..1cdf375bf --- /dev/null +++ b/application/controllers/api/Amendements_ia.php @@ -0,0 +1,73 @@ +load->library('api_auth'); + $this->load->model('api_key_model'); + $this->load->helper('url'); + } + + /** + * POST /api/amendements_ia + * Body JSON: {legislature, voteNumero, resume_ia, simplicite_ia} + * Crée ou met à jour le résumé IA pour cet amendement (upsert). + */ + public function index() + { + $method = $this->input->method(TRUE); + + $result = $this->api_auth->authenticate(); + if (isset($result['error'])) { + $this->api_auth->response($result, $result['code']); + return; + } + + $permission = $this->api_auth->check_permission('/api/amendements_ia', $method); + if ($permission !== true) { + $this->api_auth->response($permission, $permission['code']); + return; + } + + if ($method !== 'POST') { + $this->api_auth->response(['error' => true, 'message' => 'Method not allowed'], 405); + return; + } + + $input = json_decode($this->input->raw_input_stream, true); + if (!is_array($input)) { + $input = $this->input->post(); + } + + $required = ['legislature', 'voteNumero', 'resume_ia', 'simplicite_ia']; + foreach ($required as $field) { + if (!isset($input[$field]) || $input[$field] === '') { + $this->api_auth->response(['error' => true, 'message' => "Champ requis manquant : $field"], 400); + return; + } + } + + $legislature = (int) $input['legislature']; + $voteNumero = (string) $input['voteNumero']; + $resumeIa = substr((string) $input['resume_ia'], 0, 220); + $simpliciteIa = max(1, min(5, (int) $input['simplicite_ia'])); + + $this->db->query( + "INSERT INTO amendements_ia (legislature, voteNumero, resume_ia, simplicite_ia) + VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + resume_ia = VALUES(resume_ia), + simplicite_ia = VALUES(simplicite_ia), + updated_at = NOW()", + array($legislature, $voteNumero, $resumeIa, $simpliciteIa) + ); + + $this->api_auth->response(['success' => true, 'legislature' => $legislature, 'voteNumero' => $voteNumero], 200); + } +} diff --git a/application/views/dashboard-mp/amendements/index.php b/application/views/dashboard-mp/amendements/index.php new file mode 100644 index 000000000..84a0be1e2 --- /dev/null +++ b/application/views/dashboard-mp/amendements/index.php @@ -0,0 +1,275 @@ +
+
+
+
+
+ load->view('dashboard-mp/partials/breadcrumb.php', $breadcrumb) ?> +
+
+
+
+ +
+
+ + +
+ +
+

+

Dernière législature · amendements

+
+
+ +
+
+ + + session->flashdata('flash')) : ?> + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Amendement + Résumé IA + + Votants + + + + Disparité + + + + Simplicité + + + + Décrypté + +
Aucun amendement trouvé.
+
+ +
+ Leg. · n° · +
+ —' ?> + + + + 0) : + $d = (float)$a['disparite']; + $cls = $d >= 60 ? 'danger' : ($d >= 30 ? 'warning' : 'success'); + ?> + % + + + + + = 4 ? 'success' : ($a['simplicite_ia'] >= 3 ? 'warning' : 'danger'); + ?> + + + + + + + + + + + Non + + + + + + + Voir + + +
+
+
+
+ +
+
+
+ + diff --git a/scripts b/scripts index 9e6da241a..45e3eeb8f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9e6da241a89d6407dc93b7ae09875a0609b53075 +Subproject commit 45e3eeb8f9a83a67b9fab6770c032b9f5766d44d diff --git a/sql/amendements_ia.sql b/sql/amendements_ia.sql new file mode 100644 index 000000000..59bd29f50 --- /dev/null +++ b/sql/amendements_ia.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS `amendements_ia` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `legislature` INT NOT NULL, + `voteNumero` VARCHAR(20) NOT NULL, + `resume_ia` TEXT NULL, + `simplicite_ia` TINYINT UNSIGNED NULL, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY `uk_leg_vote` (`legislature`, `voteNumero`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/view b/view new file mode 100644 index 000000000..137ad7e8b --- /dev/null +++ b/view @@ -0,0 +1 @@ +/c/Users/Remi/workspace/datan/application/controllers/Newsletter.php: $this->load->view('newsletter/index', $data); From b90f0d768ea3645c0a09ed5730ba3467c7c60dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Mikel?= Date: Sat, 25 Apr 2026 20:08:38 +0200 Subject: [PATCH 02/24] Add API for IA summaries of amendments and related configurations --- .gitignore | 3 + application/config/config.php | 10 +- application/config/routes.php | 8 +- application/controllers/Admin.php | 122 ++++++++++++++++++++++- application/models/Api_key_model.php | 4 + application/models/DashboardMP_model.php | 59 ++++++++++- application/views/dashboard/header.php | 6 ++ conf/000-default.conf | 2 + docker-compose.yml | 2 +- tests/urls.txt | 3 +- 10 files changed, 206 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 27bc30d53..bb01c7e73 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ application/logs/* #custom config application/config/custom_config.php +#claude code +.claude/ + #root folder node_modules /test diff --git a/application/config/config.php b/application/config/config.php index 34e7f3dc2..db8abacec 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -326,7 +326,7 @@ | https://codeigniter.com/user_guide/libraries/encryption.html | */ -$config['encryption_key'] = isset($_SERVER['ENCRYPTION_KEY']) ? $_SERVER['ENCRYPTION_KEY'] : 'datan-dev-key-change-in-production'; +$config['encryption_key'] = ''; /* |-------------------------------------------------------------------------- @@ -385,7 +385,7 @@ $config['sess_save_path'] = '/tmp'; $config['sess_match_ip'] = FALSE; $config['sess_time_to_update'] = 300; -$config['sess_regenerate_destroy'] = TRUE; +$config['sess_regenerate_destroy'] = FALSE; /* |-------------------------------------------------------------------------- @@ -405,8 +405,8 @@ $config['cookie_prefix'] = ''; $config['cookie_domain'] = ''; $config['cookie_path'] = '/'; -$config['cookie_secure'] = (ENVIRONMENT === 'production'); -$config['cookie_httponly'] = TRUE; +$config['cookie_secure'] = FALSE; +$config['cookie_httponly'] = FALSE; /* |-------------------------------------------------------------------------- @@ -455,7 +455,7 @@ $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; $config['csrf_regenerate'] = TRUE; -$config['csrf_exclude_uris'] = array('upload/image', 'api/.*'); +$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt', 'admin/amendements/batch-summaries'); /* |-------------------------------------------------------------------------- diff --git a/application/config/routes.php b/application/config/routes.php index e044a469e..ed2f3db6f 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -52,6 +52,9 @@ // ADMIN $route['admin'] = 'admin/index'; +$route['admin/amendements'] = 'admin/amendements'; +$route['admin/amendements/decrypt'] = 'admin/amendements_decrypt'; +$route['admin/amendements/batch-summaries'] = 'admin/amendements_batch_summaries'; $route['admin/votes'] = 'admin/votes'; $route['admin/elections/modifications-mps'] = 'admin/election_modifications_mps'; $route['admin/elections/(:any)'] = 'admin/election_candidates/$1'; @@ -81,9 +84,6 @@ $route['admin/campagnes/delete/(:any)'] = 'admin/delete_campaign/$1'; $route['admin/campagnes/edit/(:any)'] = 'admin/edit_campaign/$1'; $route['admin/campagnes/toggle'] = 'admin/toggle_campaign_active'; -$route['admin/api-keys'] = 'admin/api_keys'; -$route['admin/api-keys/create'] = 'admin/api_keys_create'; -$route['admin/api-keys/revoke/(:num)'] = 'admin/api_keys_revoke/$1'; // MpDashboard @@ -211,6 +211,8 @@ $route['api/non_decrypted_votes'] = 'api/non_decrypted_votes/index'; $route['api/non_decrypted_votes/meta'] = 'api/non_decrypted_votes/meta'; $route['api/non_decrypted_votes/(:any)'] = 'api/non_decrypted_votes/index/$1'; +// Résumés IA des amendements (écriture depuis PoliticAnalysis) +$route['api/amendements_ia'] = 'api/amendements_ia/index'; // Exposés des motifs $route['api/exposes'] = 'api/exposes/index'; $route['api/exposes/meta'] = 'api/exposes/meta'; diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index bdd488315..9dd595465 100644 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -1,5 +1,5 @@ session->set_flashdata('success', 'Clé API révoquée avec succès'); redirect('admin/api-keys'); } + + public function amendements() + { + $data = $this->data; + + $sort = $this->input->get('sort') ?: 'date'; + $direction = $this->input->get('direction') ?: 'DESC'; + + $data['amendements'] = $this->DashboardMP_model->get_amendements_list($sort, $direction); + $data['sort'] = $sort; + $data['direction'] = $direction; + $data['title'] = 'Liste des amendements'; + + $data['title_meta'] = 'Amendements - Dashboard | Datan'; + $data['breadcrumb'] = array( + array('name' => 'Dashboard', 'url' => base_url() . 'admin', 'active' => FALSE), + array('name' => 'Amendements', 'url' => base_url() . 'admin/amendements', 'active' => TRUE), + ); + + $this->load->view('dashboard/header', $data); + $this->load->view('dashboard-mp/amendements/index', $data); + $this->load->view('dashboard/footer'); + } + + public function amendements_decrypt() + { + if ($this->input->method(TRUE) !== 'POST') { + show_404(); + } + + $legislature = (int) $this->input->post('legislature'); + $voteNumero = (string)$this->input->post('voteNumero'); + + if (!$legislature || !$voteNumero) { + $this->output->set_status_header(400)->set_content_type('application/json') + ->set_output(json_encode(['error' => 'legislature et voteNumero sont requis'])); + return; + } + + $pa_url = rtrim($_SERVER['POLITIC_ANALYSIS_URL'] ?? '' ?: '', '/'); + $pa_token = $_SERVER['POLITIC_ANALYSIS_TOKEN'] ?? '' ?: ''; + + if (!$pa_url) { + $this->output->set_status_header(500)->set_content_type('application/json') + ->set_output(json_encode(['error' => 'PoliticAnalysis URL non configurée'])); + return; + } + + $ch = curl_init($pa_url . '/api/decrypt-by-vote'); + curl_setopt_array($ch, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode(['legislature' => $legislature, 'voteNumero' => $voteNumero]), + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/json', + 'Authorization: Bearer ' . $pa_token, + ), + CURLOPT_TIMEOUT => 120, + )); + + $response = curl_exec($ch); + $http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curl_error = curl_error($ch); + curl_close($ch); + + if (!$http_code) { + $this->output->set_status_header(502)->set_content_type('application/json') + ->set_output(json_encode(['error' => 'PoliticAnalysis injoignable : ' . $curl_error])); + return; + } + + $this->output + ->set_status_header($http_code >= 200 && $http_code < 300 ? 200 : $http_code) + ->set_content_type('application/json') + ->set_output($response ?: json_encode(['error' => 'Réponse vide de PoliticAnalysis'])); + } + + public function amendements_batch_summaries() + { + if ($this->input->method(TRUE) !== 'POST') { + show_404(); + } + + $pa_url = rtrim($_SERVER['POLITIC_ANALYSIS_URL'] ?? '' ?: '', '/'); + $pa_token = $_SERVER['POLITIC_ANALYSIS_TOKEN'] ?? '' ?: ''; + + if (!$pa_url) { + $this->output->set_status_header(500)->set_content_type('application/json') + ->set_output(json_encode(['error' => 'PoliticAnalysis URL non configurée'])); + return; + } + + $ch = curl_init($pa_url . '/api/batch-summaries'); + curl_setopt_array($ch, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => '{}', + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/json', + 'Authorization: Bearer ' . $pa_token, + ), + CURLOPT_TIMEOUT => 600, + )); + + $response = curl_exec($ch); + $http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curl_error = curl_error($ch); + curl_close($ch); + + if (!$http_code) { + $this->output->set_status_header(502)->set_content_type('application/json') + ->set_output(json_encode(['error' => 'PoliticAnalysis injoignable : ' . $curl_error])); + return; + } + + $this->output + ->set_status_header($http_code >= 200 && $http_code < 300 ? 200 : $http_code) + ->set_content_type('application/json') + ->set_output($response ?: json_encode(['error' => 'Réponse vide de PoliticAnalysis'])); + } } ?> diff --git a/application/models/Api_key_model.php b/application/models/Api_key_model.php index 1512c588f..abe806c68 100644 --- a/application/models/Api_key_model.php +++ b/application/models/Api_key_model.php @@ -42,6 +42,10 @@ public function get_available_endpoints() '/api/non_decrypted_votes' => array( 'GET' => 'Lister les votes non décryptés' ), + // Résumés IA des amendements (écriture depuis PoliticAnalysis) + '/api/amendements_ia' => array( + 'POST' => "Créer/mettre à jour un résumé IA d'amendement" + ), // Exposés des motifs '/api/exposes' => array( 'GET' => 'Lister les exposés', diff --git a/application/models/DashboardMP_model.php b/application/models/DashboardMP_model.php index 3d702ea53..c23ff91c4 100644 --- a/application/models/DashboardMP_model.php +++ b/application/models/DashboardMP_model.php @@ -84,7 +84,6 @@ public function get_votes_explained($mpId, $published = NULL){ } public function get_vote_explained($mpId, $legislature, $voteNumero){ - echo $voteNumero; $sql = 'SELECT e.id, e.voteNumero, e.legislature, e.text AS explication, vd.title AS vote_titre, CASE WHEN e.state = 1 THEN "publié" ELSE "brouillon" END AS state, CASE @@ -151,4 +150,62 @@ public function get_explanations_by_mp($mpId) return $query->result_array(); } + /** + * Liste les votes de type amendement de la dernière législature, + * avec résumé IA, score de simplicité et statut de décryptage. + * + * @param string $sort Colonne de tri : 'date'|'votants'|'disparite'|'simplicite'|'decrypte' + * @param string $direction 'ASC'|'DESC' + */ + public function get_amendements_list($sort = 'date', $direction = 'DESC') + { + $allowed_sorts = array( + 'date' => 'vi.dateScrutin', + 'votants' => 'vi.nombreVotants', + 'disparite' => 'disparite', + 'simplicite' => 'aia.simplicite_ia', + 'decrypte' => 'decrypte', + ); + + $order_col = isset($allowed_sorts[$sort]) ? $allowed_sorts[$sort] : 'vi.dateScrutin'; + $direction = strtoupper($direction) === 'ASC' ? 'ASC' : 'DESC'; + + // Dernière législature disponible + $last_leg = $this->db->query('SELECT MAX(legislature) AS leg FROM votes_info')->row_array(); + $legislature = $last_leg['leg'] ?? 17; + + $sql = " + SELECT + vi.voteNumero, + vi.legislature, + vi.dateScrutin, + date_format(vi.dateScrutin, '%d/%m/%Y') AS dateScrutinFR, + vi.nombreVotants, + vi.decomptePour AS pour, + vi.decompteContre AS contre, + vi.decompteAbs AS abstention, + CASE + WHEN vi.nombreVotants > 0 + THEN ROUND(ABS(vi.decomptePour - vi.decompteContre) * 100 / vi.nombreVotants, 1) + ELSE 0 + END AS disparite, + aia.resume_ia, + aia.simplicite_ia, + CASE WHEN vd.id IS NOT NULL THEN 1 ELSE 0 END AS decrypte, + vd.state AS decryptage_state, + vd.id AS decryptage_id, + COALESCE(vd.title, vi.titre, vi.seanceRef) AS titre + FROM votes_info vi + LEFT JOIN amendements_ia aia + ON aia.voteNumero = vi.voteNumero AND aia.legislature = vi.legislature + LEFT JOIN votes_datan vd + ON vd.voteNumero = vi.voteNumero AND vd.legislature = vi.legislature + WHERE vi.voteType IN ('amendement', 'les amen') + AND vi.legislature = ? + ORDER BY $order_col $direction + "; + + return $this->db->query($sql, array($legislature))->result_array(); + } + } \ No newline at end of file diff --git a/application/views/dashboard/header.php b/application/views/dashboard/header.php index 1a4328691..fb4f29f63 100644 --- a/application/views/dashboard/header.php +++ b/application/views/dashboard/header.php @@ -152,6 +152,12 @@

Créer un vote

+ diff --git a/conf/000-default.conf b/conf/000-default.conf index c3675d663..52b05c140 100644 --- a/conf/000-default.conf +++ b/conf/000-default.conf @@ -47,6 +47,8 @@ SetEnv API_KEY_MAILJET ${API_KEY_MAILJET} SetEnv API_KEY_SECRETE_MAILJET ${API_KEY_SECRETE_MAILJET} SetEnv COMPOSER_AUTOLOAD ${COMPOSER_AUTOLOAD} + SetEnv POLITIC_ANALYSIS_URL ${POLITIC_ANALYSIS_URL} + SetEnv POLITIC_ANALYSIS_TOKEN ${POLITIC_ANALYSIS_TOKEN} diff --git a/docker-compose.yml b/docker-compose.yml index 5447056a5..2d2d0f1d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: - DATABASE_BACKUP=${DATABASE_BACKUP} hostname: db ports: - - "3306:3306" # mysql connection port + - "3307:3306" # mysql connection port (3306 used by local MySQL) env_file: - .env environment: diff --git a/tests/urls.txt b/tests/urls.txt index ce7376f70..e584f77c2 100644 --- a/tests/urls.txt +++ b/tests/urls.txt @@ -73,5 +73,4 @@ http://dev-datan.fr/elections/regionales-2021 http://dev-datan.fr/elections/departementales-2021 http://dev-datan.fr/parrainages-2022 http://dev-datan.fr/outils/coalition-simulateur -http://dev-datan.fr/blog/categorie/actualite-politique -http://dev-datan.fr/blog/datan/interview-la-division-du-travail-parlementaire-repose-sur-une-vision-stereotypee-de-la-femme \ No newline at end of file +http://dev-datan.fr/blog/categorie/actualite-politique \ No newline at end of file From 4249d758a7d3714cf4080281a3587c64ca2155a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Mikel?= Date: Thu, 30 Apr 2026 13:28:55 +0200 Subject: [PATCH 03/24] Implement API for IA summaries of amendments and add filtering options in the dashboard --- application/config/routes.php | 2 +- application/controllers/Admin.php | 27 ++++- ...ndements_ia.php => Api_amendements_ia.php} | 44 +++++---- application/models/DashboardMP_model.php | 47 ++++++++- .../views/dashboard-mp/amendements/index.php | 99 ++++++++++++++++--- conf/000-default.conf | 1 + scripts | 2 +- 7 files changed, 185 insertions(+), 37 deletions(-) rename application/controllers/{api/Amendements_ia.php => Api_amendements_ia.php} (51%) diff --git a/application/config/routes.php b/application/config/routes.php index ed2f3db6f..1bb04765f 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -212,7 +212,7 @@ $route['api/non_decrypted_votes/meta'] = 'api/non_decrypted_votes/meta'; $route['api/non_decrypted_votes/(:any)'] = 'api/non_decrypted_votes/index/$1'; // Résumés IA des amendements (écriture depuis PoliticAnalysis) -$route['api/amendements_ia'] = 'api/amendements_ia/index'; +$route['api/amendements_ia'] = 'api_amendements_ia/index'; // Exposés des motifs $route['api/exposes'] = 'api/exposes/index'; $route['api/exposes/meta'] = 'api/exposes/meta'; diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index 9dd595465..642a60b0c 100644 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -971,10 +971,35 @@ public function amendements() $sort = $this->input->get('sort') ?: 'date'; $direction = $this->input->get('direction') ?: 'DESC'; - $data['amendements'] = $this->DashboardMP_model->get_amendements_list($sort, $direction); + $period = $this->input->get('period'); + $date_start = $this->input->get('date_start'); + $date_end = $this->input->get('date_end'); + + $allowed_periods = array('all', '7', '30', '90', '180', '365'); + if (!in_array($period, $allowed_periods, true)) { + $period = 'all'; + } + + $valid_date = function ($d) { + return $d && preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); + }; + $date_start = $valid_date($date_start) ? $date_start : ''; + $date_end = $valid_date($date_end) ? $date_end : ''; + + $filters = array( + 'period' => $period, + 'date_start' => $date_start, + 'date_end' => $date_end, + ); + + $data['amendements'] = $this->DashboardMP_model->get_amendements_list($sort, $direction, $filters); $data['sort'] = $sort; $data['direction'] = $direction; + $data['period'] = $period; + $data['date_start'] = $date_start; + $data['date_end'] = $date_end; $data['title'] = 'Liste des amendements'; + $data['pa_public_url'] = rtrim($_SERVER['POLITIC_ANALYSIS_PUBLIC_URL'] ?? '', '/'); $data['title_meta'] = 'Amendements - Dashboard | Datan'; $data['breadcrumb'] = array( diff --git a/application/controllers/api/Amendements_ia.php b/application/controllers/Api_amendements_ia.php similarity index 51% rename from application/controllers/api/Amendements_ia.php rename to application/controllers/Api_amendements_ia.php index 1cdf375bf..2b5d52443 100644 --- a/application/controllers/api/Amendements_ia.php +++ b/application/controllers/Api_amendements_ia.php @@ -3,17 +3,28 @@ /** * API pour les résumés IA des amendements (amendements_ia) — écriture depuis PoliticAnalysis uniquement. + * + * Le controller est volontairement hors du dossier controllers/api/ : CI3 résout + * le segment "api" vers controllers/Api.php avant le sous-dossier (cf. + * CI_Router::_validate_request), donc on ne peut pas y placer un controller utile. */ -class Amendements_ia extends CI_Controller +class Api_amendements_ia extends CI_Controller { public function __construct() { parent::__construct(); - $this->load->library('api_auth'); $this->load->model('api_key_model'); $this->load->helper('url'); } + private function send_json($data, $code = 200) + { + $this->output + ->set_status_header($code) + ->set_content_type('application/json') + ->set_output(json_encode($data)); + } + /** * POST /api/amendements_ia * Body JSON: {legislature, voteNumero, resume_ia, simplicite_ia} @@ -21,23 +32,23 @@ public function __construct() */ public function index() { - $method = $this->input->method(TRUE); + if ($this->input->method(TRUE) !== 'POST') { + return $this->send_json(['error' => true, 'message' => 'Method not allowed'], 405); + } - $result = $this->api_auth->authenticate(); - if (isset($result['error'])) { - $this->api_auth->response($result, $result['code']); - return; + $auth = $this->input->get_request_header('Authorization', TRUE); + $token = (is_string($auth) && strpos($auth, 'Bearer ') === 0) ? substr($auth, 7) : ''; + if (!$token) { + return $this->send_json(['error' => true, 'message' => 'Missing Bearer token'], 401); } - $permission = $this->api_auth->check_permission('/api/amendements_ia', $method); - if ($permission !== true) { - $this->api_auth->response($permission, $permission['code']); - return; + $api_user = $this->api_key_model->validate_key($token); + if (!$api_user) { + return $this->send_json(['error' => true, 'message' => 'Invalid API key'], 401); } - if ($method !== 'POST') { - $this->api_auth->response(['error' => true, 'message' => 'Method not allowed'], 405); - return; + if (!$this->api_key_model->has_permission($api_user, '/api/amendements_ia', 'POST')) { + return $this->send_json(['error' => true, 'message' => 'Forbidden'], 403); } $input = json_decode($this->input->raw_input_stream, true); @@ -48,8 +59,7 @@ public function index() $required = ['legislature', 'voteNumero', 'resume_ia', 'simplicite_ia']; foreach ($required as $field) { if (!isset($input[$field]) || $input[$field] === '') { - $this->api_auth->response(['error' => true, 'message' => "Champ requis manquant : $field"], 400); - return; + return $this->send_json(['error' => true, 'message' => "Champ requis manquant : $field"], 400); } } @@ -68,6 +78,6 @@ public function index() array($legislature, $voteNumero, $resumeIa, $simpliciteIa) ); - $this->api_auth->response(['success' => true, 'legislature' => $legislature, 'voteNumero' => $voteNumero], 200); + return $this->send_json(['success' => true, 'legislature' => $legislature, 'voteNumero' => $voteNumero], 200); } } diff --git a/application/models/DashboardMP_model.php b/application/models/DashboardMP_model.php index c23ff91c4..019e42305 100644 --- a/application/models/DashboardMP_model.php +++ b/application/models/DashboardMP_model.php @@ -154,15 +154,19 @@ public function get_explanations_by_mp($mpId) * Liste les votes de type amendement de la dernière législature, * avec résumé IA, score de simplicité et statut de décryptage. * - * @param string $sort Colonne de tri : 'date'|'votants'|'disparite'|'simplicite'|'decrypte' - * @param string $direction 'ASC'|'DESC' + * @param string $sort Colonne de tri : 'date'|'votants'|'disparite'|'simplicite'|'decrypte' + * @param string $direction 'ASC'|'DESC' + * @param array $filters ['period' => '7'|'30'|'90'|'180'|'365'|'all', + * 'date_start' => 'YYYY-MM-DD', + * 'date_end' => 'YYYY-MM-DD'] */ - public function get_amendements_list($sort = 'date', $direction = 'DESC') + public function get_amendements_list($sort = 'date', $direction = 'DESC', $filters = array()) { $allowed_sorts = array( 'date' => 'vi.dateScrutin', 'votants' => 'vi.nombreVotants', 'disparite' => 'disparite', + 'interet' => 'interet', 'simplicite' => 'aia.simplicite_ia', 'decrypte' => 'decrypte', ); @@ -174,6 +178,32 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC') $last_leg = $this->db->query('SELECT MAX(legislature) AS leg FROM votes_info')->row_array(); $legislature = $last_leg['leg'] ?? 17; + // Filtres date + $where_date = ''; + $params = array($legislature); + + $period = isset($filters['period']) ? (string)$filters['period'] : 'all'; + $date_start = isset($filters['date_start']) ? (string)$filters['date_start'] : ''; + $date_end = isset($filters['date_end']) ? (string)$filters['date_end'] : ''; + + $is_valid_date = function ($d) { + return $d && preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); + }; + + if ($is_valid_date($date_start) || $is_valid_date($date_end)) { + if ($is_valid_date($date_start)) { + $where_date .= ' AND vi.dateScrutin >= ?'; + $params[] = $date_start; + } + if ($is_valid_date($date_end)) { + $where_date .= ' AND vi.dateScrutin <= ?'; + $params[] = $date_end; + } + } elseif (in_array($period, array('7', '30', '90', '180', '365'), true)) { + $where_date = ' AND vi.dateScrutin >= DATE_SUB(CURDATE(), INTERVAL ? DAY)'; + $params[] = (int)$period; + } + $sql = " SELECT vi.voteNumero, @@ -189,6 +219,14 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC') THEN ROUND(ABS(vi.decomptePour - vi.decompteContre) * 100 / vi.nombreVotants, 1) ELSE 0 END AS disparite, + CASE + WHEN vi.nombreVotants > 0 + THEN ROUND( + LEAST(vi.nombreVotants / 250, 1) + * (1 - ABS(vi.decomptePour - vi.decompteContre) / vi.nombreVotants) + * 100, 1) + ELSE 0 + END AS interet, aia.resume_ia, aia.simplicite_ia, CASE WHEN vd.id IS NOT NULL THEN 1 ELSE 0 END AS decrypte, @@ -202,10 +240,11 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC') ON vd.voteNumero = vi.voteNumero AND vd.legislature = vi.legislature WHERE vi.voteType IN ('amendement', 'les amen') AND vi.legislature = ? + $where_date ORDER BY $order_col $direction "; - return $this->db->query($sql, array($legislature))->result_array(); + return $this->db->query($sql, $params)->result_array(); } } \ No newline at end of file diff --git a/application/views/dashboard-mp/amendements/index.php b/application/views/dashboard-mp/amendements/index.php index 84a0be1e2..605f88a3e 100644 --- a/application/views/dashboard-mp/amendements/index.php +++ b/application/views/dashboard-mp/amendements/index.php @@ -39,13 +39,60 @@ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + + + Réinitialiser +
+
+ + Les dates personnalisées (Du / Au) ont priorité sur la période sélectionnée. + +
+
+ $period, + 'date_start' => $date_start, + 'date_end' => $date_end, + )); + function sort_url($col, $current_sort, $current_dir, $filter_qs) { $dir = ($current_sort === $col && $current_dir === 'DESC') ? 'ASC' : 'DESC'; - return base_url() . 'dashboard/amendements?sort=' . $col . '&direction=' . $dir; + $url = base_url() . 'admin/amendements?sort=' . $col . '&direction=' . $dir; + if ($filter_qs) { + $url .= '&' . $filter_qs; + } + return $url; } ?> @@ -61,22 +108,27 @@ function sort_url($col, $current_sort, $current_dir) { Résumé IA - + Votants - + Disparité - + + Intérêt + + + + Simplicité - + Décrypté @@ -86,7 +138,7 @@ function sort_url($col, $current_sort, $current_dir) { - Aucun amendement trouvé. + Aucun amendement trouvé. @@ -122,6 +174,18 @@ function sort_url($col, $current_sort, $current_dir) { + + + 0) : + $i = (float)$a['interet']; + $cls = $i >= 60 ? 'success' : ($i >= 30 ? 'warning' : ($i >= 15 ? 'info' : 'secondary')); + ?> + + + + + + - + + - + + + + Décrypter + + + @@ -212,11 +285,11 @@ class="btn btn-sm btn-primary btn-decrypt font-weight-bold" var score = data.simplicite_score; var stars = '★'.repeat(score) + '☆'.repeat(5 - score); var cls = score >= 4 ? 'success' : (score >= 3 ? 'warning' : 'danger'); - var cellSimpl = row.querySelector('td:nth-child(5)'); + var cellSimpl = row.querySelector('td:nth-child(6)'); if (cellSimpl) cellSimpl.innerHTML = '' + stars + ''; } // Badge décrypté - var cellDecrypte = row.querySelector('td:nth-child(6)'); + var cellDecrypte = row.querySelector('td:nth-child(7)'); if (cellDecrypte) cellDecrypte.innerHTML = 'Brouillon'; // Bouton btn.textContent = 'Décrypté ✓'; diff --git a/conf/000-default.conf b/conf/000-default.conf index 52b05c140..ecaff1d61 100644 --- a/conf/000-default.conf +++ b/conf/000-default.conf @@ -48,6 +48,7 @@ SetEnv API_KEY_SECRETE_MAILJET ${API_KEY_SECRETE_MAILJET} SetEnv COMPOSER_AUTOLOAD ${COMPOSER_AUTOLOAD} SetEnv POLITIC_ANALYSIS_URL ${POLITIC_ANALYSIS_URL} + SetEnv POLITIC_ANALYSIS_PUBLIC_URL ${POLITIC_ANALYSIS_PUBLIC_URL} SetEnv POLITIC_ANALYSIS_TOKEN ${POLITIC_ANALYSIS_TOKEN} diff --git a/scripts b/scripts index 45e3eeb8f..181dfdc7a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 45e3eeb8f9a83a67b9fab6770c032b9f5766d44d +Subproject commit 181dfdc7ac21861b7a1f98892c3a973bc417c3dd From f2f8122648dddab6b1b6af18ab2bd24fcd99b492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Mikel?= Date: Mon, 4 May 2026 12:29:26 +0200 Subject: [PATCH 04/24] Remove .claude/settings.json from tracking --- .claude/settings.json | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index fc11b6fa2..000000000 --- a/.claude/settings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(grep -r \"setcookie\\\\|$this->input->set_cookie\\\\|$this->load->library\\(''session''\\)\" /c/Users/Remi/workspace/datan/application/controllers --include=\"*.php\")", - "Bash(grep -n \"$this->session\" /c/Users/Remi/workspace/datan/application/views/templates/*.php)", - "Bash(grep -n \"setcookie\\\\|set_cookie\" /c/Users/Remi/workspace/datan/application/views/templates/*.php)", - "Bash(grep -n \"setcookie\\\\|set_cookie\\\\|$this->session\" /c/Users/Remi/workspace/datan/application/hooks/*.php)", - "Bash(grep -l \"extends CI_Controller\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", - "Bash(for controller:*)", - "Bash(do echo:*)", - "Read(//c/Users/Remi/workspace/datan/application/controllers/**)", - "Bash(done)", - "Bash(grep -r \"$this->load->library\\(''session''\\)\" /c/Users/Remi/workspace/datan/application/controllers/ --include=\"*.php\")", - "Bash(grep -v \"isset\\($this->session\\)\")", - "Bash(grep -v \"^\\\\s*//.*$this->session\")", - "Bash(grep -n \"isset\\($this->session\\)\" /c/Users/Remi/workspace/datan/application/views/templates/footer.php)", - "Bash(grep -n \"$this->session\" /c/Users/Remi/workspace/datan/application/views/templates/footer.php)", - "Bash(grep -n \"$this->show_popup\" /c/Users/Remi/workspace/datan/application/views/templates/footer.php)", - "Bash(grep -r \"quizBanner\\\\|newsletter.*index\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", - "Bash(grep \"extends CI_Controller\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", - "Bash(xargs -I {} basename {})", - "Bash(find /c/Users/Remi/workspace/datan/application/libraries -type f -name *.php)", - "Bash(grep -r \"CI_Controller\\\\|Auth_Controller\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", - "Bash(grep -i \"\\\\.php$\")", - "Bash(grep -rn \"$_LES\\\\|$this->upload\\\\|do_upload\" /c/Users/Remi/workspace/datan/application/controllers/)" - ] - } -} From a2fdf2d2488ed269145856f47e11cfc9a060dad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Mikel?= Date: Mon, 4 May 2026 12:29:42 +0200 Subject: [PATCH 05/24] Remove .claude/settings.local.json (already in .gitignore) --- .claude/settings.local.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index f014ae70c..000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(wmic process:*)", - "Bash(powershell -Command:*)", - "Bash(/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command \"Get-Process -Id 7748 | Select-Object Name,Id,Path\")", - "Bash(net stop:*)", - "Bash(net start:*)", - "Bash(git mv:*)", - "Bash(docker info:*)", - "Bash(docker exec:*)", - "Bash(docker stats:*)", - "Bash(grep -r \"foreach\" /c/Users/Remi/workspace/datan/application/controllers/*.php)", - "Bash(grep -r \"WHERE.*OR\\\\|UNION\\\\|subquery\\\\|IN \\(\" /c/Users/Remi/workspace/datan/application/models/*.php)" - ] - } -} From 266fd1dcc0f8a58c409155672a2b6654aef9c620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Mikel?= Date: Mon, 4 May 2026 12:37:38 +0200 Subject: [PATCH 06/24] Restore security settings and admin/api-keys routes lost during cherry-pick The cherry-pick of 2edfa818 was resolved with --theirs, which kept the pre-existing (insecure) values from the decrypt-easy base instead of master's hardened defaults. Restored: - encryption_key reads from $_SERVER['ENCRYPTION_KEY'] - sess_regenerate_destroy = TRUE - cookie_secure tied to ENVIRONMENT === 'production' - cookie_httponly = TRUE - admin/api-keys routes (admin/api_keys, create, revoke) --- application/config/config.php | 8 ++++---- application/config/routes.php | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/application/config/config.php b/application/config/config.php index db8abacec..2ef0a1393 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -326,7 +326,7 @@ | https://codeigniter.com/user_guide/libraries/encryption.html | */ -$config['encryption_key'] = ''; +$config['encryption_key'] = isset($_SERVER['ENCRYPTION_KEY']) ? $_SERVER['ENCRYPTION_KEY'] : 'datan-dev-key-change-in-production'; /* |-------------------------------------------------------------------------- @@ -385,7 +385,7 @@ $config['sess_save_path'] = '/tmp'; $config['sess_match_ip'] = FALSE; $config['sess_time_to_update'] = 300; -$config['sess_regenerate_destroy'] = FALSE; +$config['sess_regenerate_destroy'] = TRUE; /* |-------------------------------------------------------------------------- @@ -405,8 +405,8 @@ $config['cookie_prefix'] = ''; $config['cookie_domain'] = ''; $config['cookie_path'] = '/'; -$config['cookie_secure'] = FALSE; -$config['cookie_httponly'] = FALSE; +$config['cookie_secure'] = (ENVIRONMENT === 'production'); +$config['cookie_httponly'] = TRUE; /* |-------------------------------------------------------------------------- diff --git a/application/config/routes.php b/application/config/routes.php index 1bb04765f..93148b6af 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -84,6 +84,9 @@ $route['admin/campagnes/delete/(:any)'] = 'admin/delete_campaign/$1'; $route['admin/campagnes/edit/(:any)'] = 'admin/edit_campaign/$1'; $route['admin/campagnes/toggle'] = 'admin/toggle_campaign_active'; +$route['admin/api-keys'] = 'admin/api_keys'; +$route['admin/api-keys/create'] = 'admin/api_keys_create'; +$route['admin/api-keys/revoke/(:num)'] = 'admin/api_keys_revoke/$1'; // MpDashboard From c875798c6c5ba0ae52827e9a185b22e102e08d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Tue, 5 May 2026 20:02:00 +0200 Subject: [PATCH 07/24] Change directory amendements_ia Cela ne me semble pas impossible en fait de le mettre dans ce directory, no? Plus clean? --- application/config/routes.php | 2 +- .../{Api_amendements_ia.php => api/Amendements_ia.php} | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) rename application/controllers/{Api_amendements_ia.php => api/Amendements_ia.php} (90%) diff --git a/application/config/routes.php b/application/config/routes.php index 93148b6af..cc3334aa3 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -215,7 +215,7 @@ $route['api/non_decrypted_votes/meta'] = 'api/non_decrypted_votes/meta'; $route['api/non_decrypted_votes/(:any)'] = 'api/non_decrypted_votes/index/$1'; // Résumés IA des amendements (écriture depuis PoliticAnalysis) -$route['api/amendements_ia'] = 'api_amendements_ia/index'; +$route['api/amendements_ia'] = 'api/amendements_ia/index'; // Exposés des motifs $route['api/exposes'] = 'api/exposes/index'; $route['api/exposes/meta'] = 'api/exposes/meta'; diff --git a/application/controllers/Api_amendements_ia.php b/application/controllers/api/Amendements_ia.php similarity index 90% rename from application/controllers/Api_amendements_ia.php rename to application/controllers/api/Amendements_ia.php index 2b5d52443..04914a235 100644 --- a/application/controllers/Api_amendements_ia.php +++ b/application/controllers/api/Amendements_ia.php @@ -1,14 +1,12 @@ Date: Wed, 13 May 2026 10:53:39 +0200 Subject: [PATCH 08/24] Add review functionality for amendments and enhance API integration - Updated CSRF configuration to exclude new review endpoint. - Added route for reviewing amendments in routes configuration. - Implemented review handling in Admin controller. - Enhanced Amendements_ia API to include title and reviewed status. - Updated DashboardMP_model to filter reviewed amendments. - Modified dashboard view to include review checkbox and styling. - Added new script for handling review state changes. - Updated SQL schema to include title and reviewed fields in amendments_ia table. - Added Docker command for generating amendments IA. --- application/config/config.php | 2 +- application/config/routes.php | 1 + application/controllers/Admin.php | 43 +++++++--- .../controllers/api/Amendements_ia.php | 10 ++- application/models/DashboardMP_model.php | 31 +++++++ .../views/dashboard-mp/amendements/index.php | 80 +++++++++++++++++-- package.json | 1 + sql/amendements_ia.sql | 2 + 8 files changed, 150 insertions(+), 20 deletions(-) diff --git a/application/config/config.php b/application/config/config.php index 2ef0a1393..3e077da8b 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -455,7 +455,7 @@ $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; $config['csrf_regenerate'] = TRUE; -$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt', 'admin/amendements/batch-summaries'); +$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt', 'admin/amendements/batch-summaries', 'admin/amendements/review'); /* |-------------------------------------------------------------------------- diff --git a/application/config/routes.php b/application/config/routes.php index cc3334aa3..ba85186a3 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -55,6 +55,7 @@ $route['admin/amendements'] = 'admin/amendements'; $route['admin/amendements/decrypt'] = 'admin/amendements_decrypt'; $route['admin/amendements/batch-summaries'] = 'admin/amendements_batch_summaries'; +$route['admin/amendements/review'] = 'admin/amendements_review'; $route['admin/votes'] = 'admin/votes'; $route['admin/elections/modifications-mps'] = 'admin/election_modifications_mps'; $route['admin/elections/(:any)'] = 'admin/election_candidates/$1'; diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index 642a60b0c..20e270e71 100644 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -974,6 +974,7 @@ public function amendements() $period = $this->input->get('period'); $date_start = $this->input->get('date_start'); $date_end = $this->input->get('date_end'); + $hide_reviewed = filter_var($this->input->get('hide_reviewed'), FILTER_VALIDATE_BOOLEAN); $allowed_periods = array('all', '7', '30', '90', '180', '365'); if (!in_array($period, $allowed_periods, true)) { @@ -987,17 +988,19 @@ public function amendements() $date_end = $valid_date($date_end) ? $date_end : ''; $filters = array( - 'period' => $period, - 'date_start' => $date_start, - 'date_end' => $date_end, + 'period' => $period, + 'date_start' => $date_start, + 'date_end' => $date_end, + 'hide_reviewed' => $hide_reviewed, ); - $data['amendements'] = $this->DashboardMP_model->get_amendements_list($sort, $direction, $filters); - $data['sort'] = $sort; - $data['direction'] = $direction; - $data['period'] = $period; - $data['date_start'] = $date_start; - $data['date_end'] = $date_end; + $data['amendements'] = $this->DashboardMP_model->get_amendements_list($sort, $direction, $filters); + $data['sort'] = $sort; + $data['direction'] = $direction; + $data['period'] = $period; + $data['date_start'] = $date_start; + $data['date_end'] = $date_end; + $data['hide_reviewed'] = $hide_reviewed; $data['title'] = 'Liste des amendements'; $data['pa_public_url'] = rtrim($_SERVER['POLITIC_ANALYSIS_PUBLIC_URL'] ?? '', '/'); @@ -1108,5 +1111,27 @@ public function amendements_batch_summaries() ->set_content_type('application/json') ->set_output($response ?: json_encode(['error' => 'Réponse vide de PoliticAnalysis'])); } + + public function amendements_review() + { + if ($this->input->method(TRUE) !== 'POST') { + show_404(); + } + + $legislature = (int) $this->input->post('legislature'); + $voteNumero = (string)$this->input->post('voteNumero'); + $reviewed = filter_var($this->input->post('reviewed'), FILTER_VALIDATE_BOOLEAN); + + if (!$legislature || !$voteNumero) { + $this->output->set_status_header(400)->set_content_type('application/json') + ->set_output(json_encode(['error' => 'legislature et voteNumero sont requis'])); + return; + } + + $this->DashboardMP_model->set_amendement_reviewed($legislature, $voteNumero, $reviewed); + + $this->output->set_status_header(200)->set_content_type('application/json') + ->set_output(json_encode(['success' => true, 'reviewed' => $reviewed ? 1 : 0])); + } } ?> diff --git a/application/controllers/api/Amendements_ia.php b/application/controllers/api/Amendements_ia.php index 04914a235..04de1abc2 100644 --- a/application/controllers/api/Amendements_ia.php +++ b/application/controllers/api/Amendements_ia.php @@ -63,17 +63,21 @@ public function index() $legislature = (int) $input['legislature']; $voteNumero = (string) $input['voteNumero']; + $titreIa = isset($input['titre_ia']) && $input['titre_ia'] !== '' + ? substr((string) $input['titre_ia'], 0, 255) + : null; $resumeIa = substr((string) $input['resume_ia'], 0, 220); $simpliciteIa = max(1, min(5, (int) $input['simplicite_ia'])); $this->db->query( - "INSERT INTO amendements_ia (legislature, voteNumero, resume_ia, simplicite_ia) - VALUES (?, ?, ?, ?) + "INSERT INTO amendements_ia (legislature, voteNumero, titre_ia, resume_ia, simplicite_ia) + VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE + titre_ia = VALUES(titre_ia), resume_ia = VALUES(resume_ia), simplicite_ia = VALUES(simplicite_ia), updated_at = NOW()", - array($legislature, $voteNumero, $resumeIa, $simpliciteIa) + array($legislature, $voteNumero, $titreIa, $resumeIa, $simpliciteIa) ); return $this->send_json(['success' => true, 'legislature' => $legislature, 'voteNumero' => $voteNumero], 200); diff --git a/application/models/DashboardMP_model.php b/application/models/DashboardMP_model.php index 019e42305..07a4a7614 100644 --- a/application/models/DashboardMP_model.php +++ b/application/models/DashboardMP_model.php @@ -204,6 +204,12 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC', $filte $params[] = (int)$period; } + // Filtre "cacher les reviewed" + $where_reviewed = ''; + if (!empty($filters['hide_reviewed'])) { + $where_reviewed = ' AND COALESCE(aia.reviewed, 0) = 0'; + } + $sql = " SELECT vi.voteNumero, @@ -227,8 +233,10 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC', $filte * 100, 1) ELSE 0 END AS interet, + aia.titre_ia, aia.resume_ia, aia.simplicite_ia, + COALESCE(aia.reviewed, 0) AS reviewed, CASE WHEN vd.id IS NOT NULL THEN 1 ELSE 0 END AS decrypte, vd.state AS decryptage_state, vd.id AS decryptage_id, @@ -241,10 +249,33 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC', $filte WHERE vi.voteType IN ('amendement', 'les amen') AND vi.legislature = ? $where_date + $where_reviewed ORDER BY $order_col $direction "; return $this->db->query($sql, $params)->result_array(); } + /** + * Marque un amendement comme reviewed (ou non). + * Crée la ligne dans amendements_ia si elle n'existe pas encore. + * + * @return bool true si une ligne a été créée/mise à jour + */ + public function set_amendement_reviewed($legislature, $voteNumero, $reviewed) + { + $legislature = (int) $legislature; + $voteNumero = (string) $voteNumero; + $reviewed = $reviewed ? 1 : 0; + + $this->db->query( + "INSERT INTO amendements_ia (legislature, voteNumero, reviewed) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE reviewed = VALUES(reviewed), updated_at = NOW()", + array($legislature, $voteNumero, $reviewed) + ); + + return $this->db->affected_rows() > 0; + } + } \ No newline at end of file diff --git a/application/views/dashboard-mp/amendements/index.php b/application/views/dashboard-mp/amendements/index.php index 605f88a3e..63b861b23 100644 --- a/application/views/dashboard-mp/amendements/index.php +++ b/application/views/dashboard-mp/amendements/index.php @@ -1,3 +1,9 @@ +
+
+
+ > + +
+
Les dates personnalisées (Du / Au) ont priorité sur la période sélectionnée. @@ -81,11 +94,12 @@ $period, - 'date_start' => $date_start, - 'date_end' => $date_end, - )); + $filter_qs = http_build_query(array_filter(array( + 'period' => $period, + 'date_start' => $date_start, + 'date_end' => $date_end, + 'hide_reviewed' => !empty($hide_reviewed) ? '1' : '', + ))); function sort_url($col, $current_sort, $current_dir, $filter_qs) { $dir = ($current_sort === $col && $current_dir === 'DESC') ? 'ASC' : 'DESC'; $url = base_url() . 'admin/amendements?sort=' . $col . '&direction=' . $dir; @@ -106,6 +120,7 @@ function sort_url($col, $current_sort, $current_dir, $filter_qs) { Amendement + Titre IA Résumé IA @@ -132,17 +147,19 @@ function sort_url($col, $current_sort, $current_dir, $filter_qs) { Décrypté + Reviewed - Aucun amendement trouvé. + Aucun amendement trouvé. - + @@ -152,6 +169,11 @@ function sort_url($col, $current_sort, $current_dir, $filter_qs) { Leg. · n° · + + + —' ?> + + —' ?> @@ -209,6 +231,15 @@ function sort_url($col, $current_sort, $current_dir, $filter_qs) { + + + > + + @@ -344,5 +375,40 @@ class="btn btn-sm btn-outline-primary btn-decrypt font-weight-bold mr-1" batchResult.textContent = 'Erreur réseau.'; }); }); + + // Checkbox "Reviewed" + document.querySelectorAll('.chk-reviewed').forEach(function (chk) { + chk.addEventListener('change', function () { + var legislature = chk.dataset.legislature; + var voteNumero = chk.dataset.vote; + var reviewed = chk.checked ? 1 : 0; + var row = chk.closest('tr'); + + chk.disabled = true; + + fetch(BASE + 'admin/amendements/review', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, + body: 'legislature=' + encodeURIComponent(legislature) + + '&voteNumero=' + encodeURIComponent(voteNumero) + + '&reviewed=' + reviewed + }) + .then(function (r) { return r.json(); }) + .then(function (data) { + chk.disabled = false; + if (data && data.success) { + if (row) row.classList.toggle('row-reviewed', !!reviewed); + } else { + chk.checked = !reviewed; + alert('Erreur lors de la mise à jour : ' + (data && data.error ? data.error : 'inconnue')); + } + }) + .catch(function () { + chk.disabled = false; + chk.checked = !reviewed; + alert('Erreur réseau.'); + }); + }); + }); })(); diff --git a/package.json b/package.json index ad4f63338..ff68bb5a3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "docker-download": "docker exec -it datan php scripts/download.php", "docker-daily": "docker exec -it datan php scripts/daily.php", "docker-weekly": "docker exec -it datan php scripts/weekly.php", + "docker-ia": "docker exec -it datan php scripts/generate_amendements_ia.php", "docker-municipales": "docker exec -it datan php scripts/get_electoral_results.php", "docker-municipales-candidate": "docker exec -it datan php scripts/get_electoral_candidates.php", "docker-profession": "docker exec -it datan php scripts/profession.php", diff --git a/sql/amendements_ia.sql b/sql/amendements_ia.sql index 59bd29f50..28e0d47f2 100644 --- a/sql/amendements_ia.sql +++ b/sql/amendements_ia.sql @@ -2,8 +2,10 @@ CREATE TABLE IF NOT EXISTS `amendements_ia` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `legislature` INT NOT NULL, `voteNumero` VARCHAR(20) NOT NULL, + `titre_ia` VARCHAR(255) NULL, `resume_ia` TEXT NULL, `simplicite_ia` TINYINT UNSIGNED NULL, + `reviewed` TINYINT(1) NOT NULL DEFAULT 0, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `uk_leg_vote` (`legislature`, `voteNumero`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; From b19a629237b1f651b01494159b8c53b204107113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Sun, 17 May 2026 23:14:55 +0200 Subject: [PATCH 09/24] Only keep decrypt in csrf_exclude_uris --- application/config/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/config/config.php b/application/config/config.php index 3e077da8b..a49b65146 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -455,7 +455,7 @@ $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; $config['csrf_regenerate'] = TRUE; -$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt', 'admin/amendements/batch-summaries', 'admin/amendements/review'); +$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt'); /* |-------------------------------------------------------------------------- From 9395db1c52e4ce2967997386dd897edd02126f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Sun, 17 May 2026 23:32:12 +0200 Subject: [PATCH 10/24] Re-put amendements/review --- application/config/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/config/config.php b/application/config/config.php index a49b65146..11de205ca 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -455,7 +455,7 @@ $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; $config['csrf_regenerate'] = TRUE; -$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt'); +$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt', 'admin/amendements/review'); /* |-------------------------------------------------------------------------- From c487d15ea21ebb1f854c7ac2f6431a764d8f5a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Sun, 17 May 2026 23:32:28 +0200 Subject: [PATCH 11/24] Move function in another model Move it in general admin rather than dashboardMP --- application/models/Admin_model.php | 22 ++++++++++++++++++++++ application/models/DashboardMP_model.php | 22 ---------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/application/models/Admin_model.php b/application/models/Admin_model.php index 52c3bcedf..911c0cd4a 100644 --- a/application/models/Admin_model.php +++ b/application/models/Admin_model.php @@ -242,4 +242,26 @@ public function table_changes($table, $toInsert){ ); $this->db->insert('table_changes', $data); } + + /** + * Marque un amendement comme reviewed (ou non). + * Crée la ligne dans amendements_ia si elle n'existe pas encore. + * + * @return bool true si une ligne a été créée/mise à jour + */ + public function set_amendement_reviewed($legislature, $voteNumero, $reviewed) + { + $legislature = (int) $legislature; + $voteNumero = (string) $voteNumero; + $reviewed = $reviewed ? 1 : 0; + + $this->db->query( + "INSERT INTO amendements_ia (legislature, voteNumero, reviewed) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE reviewed = VALUES(reviewed), updated_at = NOW()", + array($legislature, $voteNumero, $reviewed) + ); + + return $this->db->affected_rows() > 0; + } } diff --git a/application/models/DashboardMP_model.php b/application/models/DashboardMP_model.php index 07a4a7614..89ea4239f 100644 --- a/application/models/DashboardMP_model.php +++ b/application/models/DashboardMP_model.php @@ -256,26 +256,4 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC', $filte return $this->db->query($sql, $params)->result_array(); } - /** - * Marque un amendement comme reviewed (ou non). - * Crée la ligne dans amendements_ia si elle n'existe pas encore. - * - * @return bool true si une ligne a été créée/mise à jour - */ - public function set_amendement_reviewed($legislature, $voteNumero, $reviewed) - { - $legislature = (int) $legislature; - $voteNumero = (string) $voteNumero; - $reviewed = $reviewed ? 1 : 0; - - $this->db->query( - "INSERT INTO amendements_ia (legislature, voteNumero, reviewed) - VALUES (?, ?, ?) - ON DUPLICATE KEY UPDATE reviewed = VALUES(reviewed), updated_at = NOW()", - array($legislature, $voteNumero, $reviewed) - ); - - return $this->db->affected_rows() > 0; - } - } \ No newline at end of file From 88f870da02dcc3b29ce7e12abd1b7a427a2eec63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Sun, 17 May 2026 23:34:26 +0200 Subject: [PATCH 12/24] Same : move function in admin_model --- application/models/Admin_model.php | 106 +++++++++++++++++++++++ application/models/DashboardMP_model.php | 106 ----------------------- 2 files changed, 106 insertions(+), 106 deletions(-) diff --git a/application/models/Admin_model.php b/application/models/Admin_model.php index 911c0cd4a..953d58a4c 100644 --- a/application/models/Admin_model.php +++ b/application/models/Admin_model.php @@ -243,6 +243,112 @@ public function table_changes($table, $toInsert){ $this->db->insert('table_changes', $data); } + /** + * Liste les votes de type amendement de la dernière législature, + * avec résumé IA, score de simplicité et statut de décryptage. + * + * @param string $sort Colonne de tri : 'date'|'votants'|'disparite'|'simplicite'|'decrypte' + * @param string $direction 'ASC'|'DESC' + * @param array $filters ['period' => '7'|'30'|'90'|'180'|'365'|'all', + * 'date_start' => 'YYYY-MM-DD', + * 'date_end' => 'YYYY-MM-DD'] + */ + public function get_amendements_list($sort = 'date', $direction = 'DESC', $filters = array()) + { + $allowed_sorts = array( + 'date' => 'vi.dateScrutin', + 'votants' => 'vi.nombreVotants', + 'disparite' => 'disparite', + 'interet' => 'interet', + 'simplicite' => 'aia.simplicite_ia', + 'decrypte' => 'decrypte', + ); + + $order_col = isset($allowed_sorts[$sort]) ? $allowed_sorts[$sort] : 'vi.dateScrutin'; + $direction = strtoupper($direction) === 'ASC' ? 'ASC' : 'DESC'; + + // Dernière législature disponible + $last_leg = $this->db->query('SELECT MAX(legislature) AS leg FROM votes_info')->row_array(); + $legislature = $last_leg['leg'] ?? 17; + + // Filtres date + $where_date = ''; + $params = array($legislature); + + $period = isset($filters['period']) ? (string)$filters['period'] : 'all'; + $date_start = isset($filters['date_start']) ? (string)$filters['date_start'] : ''; + $date_end = isset($filters['date_end']) ? (string)$filters['date_end'] : ''; + + $is_valid_date = function ($d) { + return $d && preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); + }; + + if ($is_valid_date($date_start) || $is_valid_date($date_end)) { + if ($is_valid_date($date_start)) { + $where_date .= ' AND vi.dateScrutin >= ?'; + $params[] = $date_start; + } + if ($is_valid_date($date_end)) { + $where_date .= ' AND vi.dateScrutin <= ?'; + $params[] = $date_end; + } + } elseif (in_array($period, array('7', '30', '90', '180', '365'), true)) { + $where_date = ' AND vi.dateScrutin >= DATE_SUB(CURDATE(), INTERVAL ? DAY)'; + $params[] = (int)$period; + } + + // Filtre "cacher les reviewed" + $where_reviewed = ''; + if (!empty($filters['hide_reviewed'])) { + $where_reviewed = ' AND COALESCE(aia.reviewed, 0) = 0'; + } + + $sql = " + SELECT + vi.voteNumero, + vi.legislature, + vi.dateScrutin, + date_format(vi.dateScrutin, '%d/%m/%Y') AS dateScrutinFR, + vi.nombreVotants, + vi.decomptePour AS pour, + vi.decompteContre AS contre, + vi.decompteAbs AS abstention, + CASE + WHEN vi.nombreVotants > 0 + THEN ROUND(ABS(vi.decomptePour - vi.decompteContre) * 100 / vi.nombreVotants, 1) + ELSE 0 + END AS disparite, + CASE + WHEN vi.nombreVotants > 0 + THEN ROUND( + LEAST(vi.nombreVotants / 250, 1) + * (1 - ABS(vi.decomptePour - vi.decompteContre) / vi.nombreVotants) + * 100, 1) + ELSE 0 + END AS interet, + aia.titre_ia, + aia.resume_ia, + aia.simplicite_ia, + COALESCE(aia.reviewed, 0) AS reviewed, + CASE WHEN vd.id IS NOT NULL THEN 1 ELSE 0 END AS decrypte, + vd.state AS decryptage_state, + vd.id AS decryptage_id, + COALESCE(vd.title, vi.titre, vi.seanceRef) AS titre + FROM votes_info vi + LEFT JOIN amendements_ia aia + ON aia.voteNumero = vi.voteNumero AND aia.legislature = vi.legislature + LEFT JOIN votes_datan vd + ON vd.voteNumero = vi.voteNumero AND vd.legislature = vi.legislature + WHERE vi.voteType IN ('amendement', 'les amen') + AND vi.legislature = ? + $where_date + $where_reviewed + ORDER BY $order_col $direction + "; + + return $this->db->query($sql, $params)->result_array(); + } + /** * Marque un amendement comme reviewed (ou non). * Crée la ligne dans amendements_ia si elle n'existe pas encore. diff --git a/application/models/DashboardMP_model.php b/application/models/DashboardMP_model.php index 89ea4239f..03b092c01 100644 --- a/application/models/DashboardMP_model.php +++ b/application/models/DashboardMP_model.php @@ -150,110 +150,4 @@ public function get_explanations_by_mp($mpId) return $query->result_array(); } - /** - * Liste les votes de type amendement de la dernière législature, - * avec résumé IA, score de simplicité et statut de décryptage. - * - * @param string $sort Colonne de tri : 'date'|'votants'|'disparite'|'simplicite'|'decrypte' - * @param string $direction 'ASC'|'DESC' - * @param array $filters ['period' => '7'|'30'|'90'|'180'|'365'|'all', - * 'date_start' => 'YYYY-MM-DD', - * 'date_end' => 'YYYY-MM-DD'] - */ - public function get_amendements_list($sort = 'date', $direction = 'DESC', $filters = array()) - { - $allowed_sorts = array( - 'date' => 'vi.dateScrutin', - 'votants' => 'vi.nombreVotants', - 'disparite' => 'disparite', - 'interet' => 'interet', - 'simplicite' => 'aia.simplicite_ia', - 'decrypte' => 'decrypte', - ); - - $order_col = isset($allowed_sorts[$sort]) ? $allowed_sorts[$sort] : 'vi.dateScrutin'; - $direction = strtoupper($direction) === 'ASC' ? 'ASC' : 'DESC'; - - // Dernière législature disponible - $last_leg = $this->db->query('SELECT MAX(legislature) AS leg FROM votes_info')->row_array(); - $legislature = $last_leg['leg'] ?? 17; - - // Filtres date - $where_date = ''; - $params = array($legislature); - - $period = isset($filters['period']) ? (string)$filters['period'] : 'all'; - $date_start = isset($filters['date_start']) ? (string)$filters['date_start'] : ''; - $date_end = isset($filters['date_end']) ? (string)$filters['date_end'] : ''; - - $is_valid_date = function ($d) { - return $d && preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); - }; - - if ($is_valid_date($date_start) || $is_valid_date($date_end)) { - if ($is_valid_date($date_start)) { - $where_date .= ' AND vi.dateScrutin >= ?'; - $params[] = $date_start; - } - if ($is_valid_date($date_end)) { - $where_date .= ' AND vi.dateScrutin <= ?'; - $params[] = $date_end; - } - } elseif (in_array($period, array('7', '30', '90', '180', '365'), true)) { - $where_date = ' AND vi.dateScrutin >= DATE_SUB(CURDATE(), INTERVAL ? DAY)'; - $params[] = (int)$period; - } - - // Filtre "cacher les reviewed" - $where_reviewed = ''; - if (!empty($filters['hide_reviewed'])) { - $where_reviewed = ' AND COALESCE(aia.reviewed, 0) = 0'; - } - - $sql = " - SELECT - vi.voteNumero, - vi.legislature, - vi.dateScrutin, - date_format(vi.dateScrutin, '%d/%m/%Y') AS dateScrutinFR, - vi.nombreVotants, - vi.decomptePour AS pour, - vi.decompteContre AS contre, - vi.decompteAbs AS abstention, - CASE - WHEN vi.nombreVotants > 0 - THEN ROUND(ABS(vi.decomptePour - vi.decompteContre) * 100 / vi.nombreVotants, 1) - ELSE 0 - END AS disparite, - CASE - WHEN vi.nombreVotants > 0 - THEN ROUND( - LEAST(vi.nombreVotants / 250, 1) - * (1 - ABS(vi.decomptePour - vi.decompteContre) / vi.nombreVotants) - * 100, 1) - ELSE 0 - END AS interet, - aia.titre_ia, - aia.resume_ia, - aia.simplicite_ia, - COALESCE(aia.reviewed, 0) AS reviewed, - CASE WHEN vd.id IS NOT NULL THEN 1 ELSE 0 END AS decrypte, - vd.state AS decryptage_state, - vd.id AS decryptage_id, - COALESCE(vd.title, vi.titre, vi.seanceRef) AS titre - FROM votes_info vi - LEFT JOIN amendements_ia aia - ON aia.voteNumero = vi.voteNumero AND aia.legislature = vi.legislature - LEFT JOIN votes_datan vd - ON vd.voteNumero = vi.voteNumero AND vd.legislature = vi.legislature - WHERE vi.voteType IN ('amendement', 'les amen') - AND vi.legislature = ? - $where_date - $where_reviewed - ORDER BY $order_col $direction - "; - - return $this->db->query($sql, $params)->result_array(); - } - } \ No newline at end of file From 43b4020469f4501b78ca174fef434aeb65892223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Sun, 17 May 2026 23:38:40 +0200 Subject: [PATCH 13/24] Update Admin.php --- application/controllers/Admin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index 20e270e71..a33065a6a 100644 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -994,7 +994,7 @@ public function amendements() 'hide_reviewed' => $hide_reviewed, ); - $data['amendements'] = $this->DashboardMP_model->get_amendements_list($sort, $direction, $filters); + $data['amendements'] = $this->admin_model->get_amendements_list($sort, $direction, $filters); $data['sort'] = $sort; $data['direction'] = $direction; $data['period'] = $period; @@ -1128,7 +1128,7 @@ public function amendements_review() return; } - $this->DashboardMP_model->set_amendement_reviewed($legislature, $voteNumero, $reviewed); + $this->admin_model->set_amendement_reviewed($legislature, $voteNumero, $reviewed); $this->output->set_status_header(200)->set_content_type('application/json') ->set_output(json_encode(['success' => true, 'reviewed' => $reviewed ? 1 : 0])); From cccff00b5c2ee345fd44ed328f400071ab14ea64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Mikel?= Date: Mon, 18 May 2026 12:25:19 +0200 Subject: [PATCH 14/24] small fixes --- application/controllers/Admin.php | 8 +++++++- application/controllers/api/Amendements_ia.php | 2 -- application/models/Admin_model.php | 6 +++--- application/views/dashboard-mp/amendements/index.php | 1 + docker-compose.yml | 2 +- scripts | 2 +- view | 1 - 7 files changed, 13 insertions(+), 9 deletions(-) delete mode 100644 view diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index a33065a6a..cfe70fc5e 100644 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -1128,7 +1128,13 @@ public function amendements_review() return; } - $this->admin_model->set_amendement_reviewed($legislature, $voteNumero, $reviewed); + $ok = $this->admin_model->set_amendement_reviewed($legislature, $voteNumero, $reviewed); + + if (!$ok) { + $this->output->set_status_header(500)->set_content_type('application/json') + ->set_output(json_encode(['error' => "Échec de l'enregistrement (vérifier que la table amendements_ia et sa clé unique uk_leg_vote existent)"])); + return; + } $this->output->set_status_header(200)->set_content_type('application/json') ->set_output(json_encode(['success' => true, 'reviewed' => $reviewed ? 1 : 0])); diff --git a/application/controllers/api/Amendements_ia.php b/application/controllers/api/Amendements_ia.php index 04de1abc2..2756bceb0 100644 --- a/application/controllers/api/Amendements_ia.php +++ b/application/controllers/api/Amendements_ia.php @@ -1,8 +1,6 @@ db->query( + $ok = $this->db->query( "INSERT INTO amendements_ia (legislature, voteNumero, reviewed) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE reviewed = VALUES(reviewed), updated_at = NOW()", array($legislature, $voteNumero, $reviewed) ); - return $this->db->affected_rows() > 0; + return $ok !== FALSE; } } diff --git a/application/views/dashboard-mp/amendements/index.php b/application/views/dashboard-mp/amendements/index.php index 63b861b23..c1c87030a 100644 --- a/application/views/dashboard-mp/amendements/index.php +++ b/application/views/dashboard-mp/amendements/index.php @@ -79,6 +79,7 @@
>
diff --git a/docker-compose.yml b/docker-compose.yml index 2d2d0f1d8..5447056a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: - DATABASE_BACKUP=${DATABASE_BACKUP} hostname: db ports: - - "3307:3306" # mysql connection port (3306 used by local MySQL) + - "3306:3306" # mysql connection port env_file: - .env environment: diff --git a/scripts b/scripts index 181dfdc7a..9e6da241a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 181dfdc7ac21861b7a1f98892c3a973bc417c3dd +Subproject commit 9e6da241a89d6407dc93b7ae09875a0609b53075 diff --git a/view b/view deleted file mode 100644 index 137ad7e8b..000000000 --- a/view +++ /dev/null @@ -1 +0,0 @@ -/c/Users/Remi/workspace/datan/application/controllers/Newsletter.php: $this->load->view('newsletter/index', $data); From 99316c2fb7cb0ab8efb2498a2a1181a11c1129d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 15:16:21 +0200 Subject: [PATCH 15/24] Remove because no longer with politicanalysis --- .../controllers/api/Amendements_ia.php | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 application/controllers/api/Amendements_ia.php diff --git a/application/controllers/api/Amendements_ia.php b/application/controllers/api/Amendements_ia.php deleted file mode 100644 index 2756bceb0..000000000 --- a/application/controllers/api/Amendements_ia.php +++ /dev/null @@ -1,83 +0,0 @@ -load->model('api_key_model'); - $this->load->helper('url'); - } - - private function send_json($data, $code = 200) - { - $this->output - ->set_status_header($code) - ->set_content_type('application/json') - ->set_output(json_encode($data)); - } - - /** - * POST /api/amendements_ia - * Body JSON: {legislature, voteNumero, resume_ia, simplicite_ia} - * Crée ou met à jour le résumé IA pour cet amendement (upsert). - */ - public function index() - { - if ($this->input->method(TRUE) !== 'POST') { - return $this->send_json(['error' => true, 'message' => 'Method not allowed'], 405); - } - - $auth = $this->input->get_request_header('Authorization', TRUE); - $token = (is_string($auth) && strpos($auth, 'Bearer ') === 0) ? substr($auth, 7) : ''; - if (!$token) { - return $this->send_json(['error' => true, 'message' => 'Missing Bearer token'], 401); - } - - $api_user = $this->api_key_model->validate_key($token); - if (!$api_user) { - return $this->send_json(['error' => true, 'message' => 'Invalid API key'], 401); - } - - if (!$this->api_key_model->has_permission($api_user, '/api/amendements_ia', 'POST')) { - return $this->send_json(['error' => true, 'message' => 'Forbidden'], 403); - } - - $input = json_decode($this->input->raw_input_stream, true); - if (!is_array($input)) { - $input = $this->input->post(); - } - - $required = ['legislature', 'voteNumero', 'resume_ia', 'simplicite_ia']; - foreach ($required as $field) { - if (!isset($input[$field]) || $input[$field] === '') { - return $this->send_json(['error' => true, 'message' => "Champ requis manquant : $field"], 400); - } - } - - $legislature = (int) $input['legislature']; - $voteNumero = (string) $input['voteNumero']; - $titreIa = isset($input['titre_ia']) && $input['titre_ia'] !== '' - ? substr((string) $input['titre_ia'], 0, 255) - : null; - $resumeIa = substr((string) $input['resume_ia'], 0, 220); - $simpliciteIa = max(1, min(5, (int) $input['simplicite_ia'])); - - $this->db->query( - "INSERT INTO amendements_ia (legislature, voteNumero, titre_ia, resume_ia, simplicite_ia) - VALUES (?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - titre_ia = VALUES(titre_ia), - resume_ia = VALUES(resume_ia), - simplicite_ia = VALUES(simplicite_ia), - updated_at = NOW()", - array($legislature, $voteNumero, $titreIa, $resumeIa, $simpliciteIa) - ); - - return $this->send_json(['success' => true, 'legislature' => $legislature, 'voteNumero' => $voteNumero], 200); - } -} From 1ca56e7134e6ec78d870961790c69697c48a9d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 15:34:17 +0200 Subject: [PATCH 16/24] Add data table --- application/views/dashboard/footer.php | 38 +++++++++++++++++++------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/application/views/dashboard/footer.php b/application/views/dashboard/footer.php index b96e7b463..0713ad25d 100644 --- a/application/views/dashboard/footer.php +++ b/application/views/dashboard/footer.php @@ -68,12 +68,27 @@ $(document).ready(function() { - $('#table_votes_datan').dataTable({ - "order": [[0, "desc"]], - language: french - }); + if ($('#table_votes_datan').length && !$.fn.DataTable.isDataTable('#table_votes_datan')) { + $('#table_votes_datan').dataTable({ + "order": [[0, "desc"]], + language: french + }); + } + + if ($('#table-amendements').length && !$.fn.DataTable.isDataTable('#table-amendements')) { + $('#table-amendements').DataTable({ + fixedHeader: true, + paging: true, + order: [], + language: french, + columnDefs: [ + { orderable: false, targets: [9] } + ] + }); + } - $('#table_votes_an').DataTable( { + if ($('#table_votes_an').length && !$.fn.DataTable.isDataTable('#table_votes_an')) { + $('#table_votes_an').DataTable( { dom: 'Bfrtip', //"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]], fixedHeader: true, @@ -98,11 +113,14 @@ show: ':hidden' } ] - } ); - - $('#table_analyses').DataTable({ - "order": [[0, "desc"]] - }); + } ); + } + + if ($('#table_analyses').length && !$.fn.DataTable.isDataTable('#table_analyses')) { + $('#table_analyses').DataTable({ + "order": [[0, "desc"]] + }); + } for(let link of $('.nav-treeview .nav-link')){ let searchParams = new URLSearchParams(window.location.search); From 90939a0376a1324ad486698a5614125797d6b6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 16:00:15 +0200 Subject: [PATCH 17/24] Improve design of table --- .../views/dashboard-mp/amendements/index.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/application/views/dashboard-mp/amendements/index.php b/application/views/dashboard-mp/amendements/index.php index c1c87030a..b8b0c5bd3 100644 --- a/application/views/dashboard-mp/amendements/index.php +++ b/application/views/dashboard-mp/amendements/index.php @@ -47,7 +47,7 @@
-
+
@@ -113,10 +113,10 @@ function sort_url($col, $current_sort, $current_dir, $filter_qs) {
-
+
- - +
+ @@ -165,7 +165,7 @@ class=""> From 516952a83d2299af92fa98e1029281b578ac0a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 16:01:52 +0200 Subject: [PATCH 18/24] Sorting already dealt with with datatable --- .../views/dashboard-mp/amendements/index.php | 52 ++----------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/application/views/dashboard-mp/amendements/index.php b/application/views/dashboard-mp/amendements/index.php index b8b0c5bd3..7fc648554 100644 --- a/application/views/dashboard-mp/amendements/index.php +++ b/application/views/dashboard-mp/amendements/index.php @@ -71,8 +71,6 @@ value="">
- - Réinitialiser
@@ -91,26 +89,6 @@ - - $period, - 'date_start' => $date_start, - 'date_end' => $date_end, - 'hide_reviewed' => !empty($hide_reviewed) ? '1' : '', - ))); - function sort_url($col, $current_sort, $current_dir, $filter_qs) { - $dir = ($current_sort === $col && $current_dir === 'DESC') ? 'ASC' : 'DESC'; - $url = base_url() . 'admin/amendements?sort=' . $col . '&direction=' . $dir; - if ($filter_qs) { - $url .= '&' . $filter_qs; - } - return $url; - } - ?> -
@@ -123,31 +101,11 @@ function sort_url($col, $current_sort, $current_dir, $filter_qs) {
- - - - - + + + + + From 20af24578fd8c5b0e726be92c8b0331ac75758af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 16:28:00 +0200 Subject: [PATCH 19/24] Don't display amendments already decrypted --- application/models/Admin_model.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/application/models/Admin_model.php b/application/models/Admin_model.php index 3784e335d..56ebc1dbe 100644 --- a/application/models/Admin_model.php +++ b/application/models/Admin_model.php @@ -330,9 +330,6 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC', $filte aia.resume_ia, aia.simplicite_ia, COALESCE(aia.reviewed, 0) AS reviewed, - CASE WHEN vd.id IS NOT NULL THEN 1 ELSE 0 END AS decrypte, - vd.state AS decryptage_state, - vd.id AS decryptage_id, COALESCE(vd.title, vi.titre, vi.seanceRef) AS titre FROM votes_info vi LEFT JOIN amendements_ia aia @@ -341,6 +338,7 @@ public function get_amendements_list($sort = 'date', $direction = 'DESC', $filte ON vd.voteNumero = vi.voteNumero AND vd.legislature = vi.legislature WHERE vi.voteType IN ('amendement', 'les amen') AND vi.legislature = ? + AND vd.id IS NULL $where_date $where_reviewed ORDER BY $order_col $direction From ff7b1956c609e641327ec2b0ef12095a51c04998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 16:30:59 +0200 Subject: [PATCH 20/24] Clean view --- .../views/dashboard-mp/amendements/index.php | 128 ------------------ application/views/dashboard/footer.php | 5 +- 2 files changed, 1 insertion(+), 132 deletions(-) diff --git a/application/views/dashboard-mp/amendements/index.php b/application/views/dashboard-mp/amendements/index.php index 7fc648554..9d60d4401 100644 --- a/application/views/dashboard-mp/amendements/index.php +++ b/application/views/dashboard-mp/amendements/index.php @@ -30,11 +30,6 @@

Dernière législature · amendements

-
- -
@@ -42,9 +37,6 @@ - - -
@@ -105,7 +97,6 @@
- @@ -179,17 +170,6 @@ class=""> - - - @@ -242,99 +207,6 @@ class="btn btn-sm btn-outline-primary btn-decrypt font-weight-bold mr-1" (function () { var BASE = ''; - // Bouton "Décrypter" par amendement - document.querySelectorAll('.btn-decrypt').forEach(function (btn) { - btn.addEventListener('click', function () { - var legislature = btn.dataset.legislature; - var voteNumero = btn.dataset.vote; - var row = btn.closest('tr'); - - btn.disabled = true; - btn.textContent = 'En cours…'; - - fetch(BASE + 'admin/amendements/decrypt', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, - body: 'legislature=' + encodeURIComponent(legislature) + '&voteNumero=' + encodeURIComponent(voteNumero) - }) - .then(function (r) { return r.json(); }) - .then(function (data) { - if (data.error) { - btn.textContent = 'Erreur'; - btn.classList.replace('btn-primary', 'btn-danger'); - btn.disabled = false; - alert('Erreur : ' + data.error); - } else { - // Résumé IA - if (data.resume_ia) { - var cellResume = row.querySelector('td:nth-child(2)'); - if (cellResume) cellResume.textContent = data.resume_ia; - } - // Simplicité - if (data.simplicite_score) { - var score = data.simplicite_score; - var stars = '★'.repeat(score) + '☆'.repeat(5 - score); - var cls = score >= 4 ? 'success' : (score >= 3 ? 'warning' : 'danger'); - var cellSimpl = row.querySelector('td:nth-child(6)'); - if (cellSimpl) cellSimpl.innerHTML = '' + stars + ''; - } - // Badge décrypté - var cellDecrypte = row.querySelector('td:nth-child(7)'); - if (cellDecrypte) cellDecrypte.innerHTML = 'Brouillon'; - // Bouton - btn.textContent = 'Décrypté ✓'; - btn.classList.replace('btn-primary', 'btn-success'); - } - }) - .catch(function () { - btn.textContent = 'Erreur réseau'; - btn.classList.replace('btn-primary', 'btn-danger'); - btn.disabled = false; - }); - }); - }); - - // Bouton "Générer tous les résumés IA" - var batchBtn = document.getElementById('btn-batch-summaries'); - var batchResult = document.getElementById('batch-result'); - - batchBtn.addEventListener('click', function () { - if (!confirm('Lancer la génération des résumés IA pour tous les amendements sans résumé ? Cela peut prendre plusieurs minutes.')) { - return; - } - batchBtn.disabled = true; - batchBtn.textContent = 'Génération en cours…'; - batchResult.className = 'alert alert-info'; - batchResult.textContent = 'Requête envoyée à PoliticAnalysis…'; - - fetch(BASE + 'admin/amendements/batch-summaries', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, - body: '' - }) - .then(function (r) { return r.json(); }) - .then(function (data) { - batchBtn.disabled = false; - batchBtn.textContent = 'Générer tous les résumés IA'; - if (data.error) { - batchResult.className = 'alert alert-danger'; - batchResult.textContent = 'Erreur : ' + data.error; - } else { - batchResult.className = 'alert alert-success'; - batchResult.textContent = - 'Terminé · ' + data.generated + ' générés, ' + data.skipped + ' déjà faits' - + (data.errors && data.errors.length ? ' · ' + data.errors.length + ' erreur(s)' : ''); - setTimeout(function () { location.reload(); }, 2000); - } - }) - .catch(function () { - batchBtn.disabled = false; - batchBtn.textContent = 'Générer tous les résumés IA'; - batchResult.className = 'alert alert-danger'; - batchResult.textContent = 'Erreur réseau.'; - }); - }); - // Checkbox "Reviewed" document.querySelectorAll('.chk-reviewed').forEach(function (chk) { chk.addEventListener('change', function () { diff --git a/application/views/dashboard/footer.php b/application/views/dashboard/footer.php index 0713ad25d..6fded5006 100644 --- a/application/views/dashboard/footer.php +++ b/application/views/dashboard/footer.php @@ -79,11 +79,8 @@ $('#table-amendements').DataTable({ fixedHeader: true, paging: true, - order: [], + order: [[ 5, "desc" ]], language: french, - columnDefs: [ - { orderable: false, targets: [9] } - ] }); } From f588cea02b37bdb5858d083021e1c36bddaf6ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 23:52:19 +0200 Subject: [PATCH 21/24] amendements_batch_summaries no longer used --- application/config/routes.php | 1 - application/controllers/Admin.php | 44 ------------------------------- 2 files changed, 45 deletions(-) diff --git a/application/config/routes.php b/application/config/routes.php index ba85186a3..ef0155679 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -54,7 +54,6 @@ $route['admin'] = 'admin/index'; $route['admin/amendements'] = 'admin/amendements'; $route['admin/amendements/decrypt'] = 'admin/amendements_decrypt'; -$route['admin/amendements/batch-summaries'] = 'admin/amendements_batch_summaries'; $route['admin/amendements/review'] = 'admin/amendements_review'; $route['admin/votes'] = 'admin/votes'; $route['admin/elections/modifications-mps'] = 'admin/election_modifications_mps'; diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index cfe70fc5e..6288b56ca 100644 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -1068,50 +1068,6 @@ public function amendements_decrypt() ->set_output($response ?: json_encode(['error' => 'Réponse vide de PoliticAnalysis'])); } - public function amendements_batch_summaries() - { - if ($this->input->method(TRUE) !== 'POST') { - show_404(); - } - - $pa_url = rtrim($_SERVER['POLITIC_ANALYSIS_URL'] ?? '' ?: '', '/'); - $pa_token = $_SERVER['POLITIC_ANALYSIS_TOKEN'] ?? '' ?: ''; - - if (!$pa_url) { - $this->output->set_status_header(500)->set_content_type('application/json') - ->set_output(json_encode(['error' => 'PoliticAnalysis URL non configurée'])); - return; - } - - $ch = curl_init($pa_url . '/api/batch-summaries'); - curl_setopt_array($ch, array( - CURLOPT_RETURNTRANSFER => true, - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => '{}', - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/json', - 'Authorization: Bearer ' . $pa_token, - ), - CURLOPT_TIMEOUT => 600, - )); - - $response = curl_exec($ch); - $http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); - $curl_error = curl_error($ch); - curl_close($ch); - - if (!$http_code) { - $this->output->set_status_header(502)->set_content_type('application/json') - ->set_output(json_encode(['error' => 'PoliticAnalysis injoignable : ' . $curl_error])); - return; - } - - $this->output - ->set_status_header($http_code >= 200 && $http_code < 300 ? 200 : $http_code) - ->set_content_type('application/json') - ->set_output($response ?: json_encode(['error' => 'Réponse vide de PoliticAnalysis'])); - } - public function amendements_review() { if ($this->input->method(TRUE) !== 'POST') { From 3571639a664633ff8225307414d46474c538c457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 23:54:21 +0200 Subject: [PATCH 22/24] amendements_decrypt no longer used --- application/config/config.php | 2 +- application/config/routes.php | 1 - application/controllers/Admin.php | 53 ------------------------------- 3 files changed, 1 insertion(+), 55 deletions(-) diff --git a/application/config/config.php b/application/config/config.php index 11de205ca..eb54b9763 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -455,7 +455,7 @@ $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; $config['csrf_regenerate'] = TRUE; -$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/decrypt', 'admin/amendements/review'); +$config['csrf_exclude_uris'] = array('upload/image', 'api/.*', 'admin/amendements/review'); /* |-------------------------------------------------------------------------- diff --git a/application/config/routes.php b/application/config/routes.php index ef0155679..da43ab21f 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -53,7 +53,6 @@ // ADMIN $route['admin'] = 'admin/index'; $route['admin/amendements'] = 'admin/amendements'; -$route['admin/amendements/decrypt'] = 'admin/amendements_decrypt'; $route['admin/amendements/review'] = 'admin/amendements_review'; $route['admin/votes'] = 'admin/votes'; $route['admin/elections/modifications-mps'] = 'admin/election_modifications_mps'; diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index 6288b56ca..c25e01ae8 100644 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -1015,59 +1015,6 @@ public function amendements() $this->load->view('dashboard/footer'); } - public function amendements_decrypt() - { - if ($this->input->method(TRUE) !== 'POST') { - show_404(); - } - - $legislature = (int) $this->input->post('legislature'); - $voteNumero = (string)$this->input->post('voteNumero'); - - if (!$legislature || !$voteNumero) { - $this->output->set_status_header(400)->set_content_type('application/json') - ->set_output(json_encode(['error' => 'legislature et voteNumero sont requis'])); - return; - } - - $pa_url = rtrim($_SERVER['POLITIC_ANALYSIS_URL'] ?? '' ?: '', '/'); - $pa_token = $_SERVER['POLITIC_ANALYSIS_TOKEN'] ?? '' ?: ''; - - if (!$pa_url) { - $this->output->set_status_header(500)->set_content_type('application/json') - ->set_output(json_encode(['error' => 'PoliticAnalysis URL non configurée'])); - return; - } - - $ch = curl_init($pa_url . '/api/decrypt-by-vote'); - curl_setopt_array($ch, array( - CURLOPT_RETURNTRANSFER => true, - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => json_encode(['legislature' => $legislature, 'voteNumero' => $voteNumero]), - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/json', - 'Authorization: Bearer ' . $pa_token, - ), - CURLOPT_TIMEOUT => 120, - )); - - $response = curl_exec($ch); - $http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); - $curl_error = curl_error($ch); - curl_close($ch); - - if (!$http_code) { - $this->output->set_status_header(502)->set_content_type('application/json') - ->set_output(json_encode(['error' => 'PoliticAnalysis injoignable : ' . $curl_error])); - return; - } - - $this->output - ->set_status_header($http_code >= 200 && $http_code < 300 ? 200 : $http_code) - ->set_content_type('application/json') - ->set_output($response ?: json_encode(['error' => 'Réponse vide de PoliticAnalysis'])); - } - public function amendements_review() { if ($this->input->method(TRUE) !== 'POST') { From 382c348c8506ad5609cb003f8f5ae2bcf6549ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Mon, 18 May 2026 23:57:11 +0200 Subject: [PATCH 23/24] Seem useless to me now? --- conf/000-default.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/conf/000-default.conf b/conf/000-default.conf index ecaff1d61..a1cc86dfa 100644 --- a/conf/000-default.conf +++ b/conf/000-default.conf @@ -47,9 +47,7 @@ SetEnv API_KEY_MAILJET ${API_KEY_MAILJET} SetEnv API_KEY_SECRETE_MAILJET ${API_KEY_SECRETE_MAILJET} SetEnv COMPOSER_AUTOLOAD ${COMPOSER_AUTOLOAD} - SetEnv POLITIC_ANALYSIS_URL ${POLITIC_ANALYSIS_URL} SetEnv POLITIC_ANALYSIS_PUBLIC_URL ${POLITIC_ANALYSIS_PUBLIC_URL} - SetEnv POLITIC_ANALYSIS_TOKEN ${POLITIC_ANALYSIS_TOKEN} From cf1083ec279311400970011ab92544c3577a74a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Awenig=20Mari=C3=A9?= Date: Tue, 19 May 2026 00:00:08 +0200 Subject: [PATCH 24/24] Update .env.dist --- .env.dist | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.dist b/.env.dist index d7426001e..ab52fe734 100644 --- a/.env.dist +++ b/.env.dist @@ -16,4 +16,5 @@ BASE_URL=http://dev-datan.fr API_KEY_NOBG= API_KEY_MAILJET= API_KEY_SECRETE_MAILJET= -COMPOSER_AUTOLOAD=application/config/autoload.php \ No newline at end of file +COMPOSER_AUTOLOAD=application/config/autoload.php +POLITIC_ANALYSIS_PUBLIC_URL= \ No newline at end of file
Amendement @@ -124,27 +124,27 @@ function sort_url($col, $current_sort, $current_dir, $filter_qs) { Titre IA Résumé IA - + Votants - + Disparité - + Intérêt - + Simplicité - + Décrypté
- +
Leg. · n° ·
Titre IA Résumé IA - - Votants - - - - Disparité - - - - Intérêt - - - - Simplicité - - - - Décrypté - - VotantsDisparitéIntérêtSimplicitéDécrypté Reviewed
Disparité Intérêt SimplicitéDécrypté Reviewed
- - - - - - Non - - - - - - - - Voir - -