Data-driven policies: sea-rescue operations in France

Standard

In October 2019, I had the chance to give a class at the the Paris Institute of Political Studies, Sciences Po. I talked about what I worked on in 2018: sea-rescue operations carried out by France. Back then, I worked at the maritime affairs, part of the Ministry of Ecology. This project was named PrédiSauvetage.

As a trained software engineer myself, it’s always a chance to get to teach about other ways to work on public policies.

You can check out my presentation at the end of the post or directly on Google Slides.

Speed up checkout on CircleCI with caching

Standard

If you’ve got a long Git history or large files changing frequently in your repository, checking out the repository can take a good amount of time in your CircleCI jobs. It’s annoying when most of the time is spent on non useful tasks like checking out your code or downloading dependencies. In these situations, caching is what you need. The idea is that checking out your code takes time if you start from a blank state. If you already have a recent previous commit locally, checking out new changes will be far faster.

You can cache your Git folder with the following configuration.

    steps:
      - restore_cache:
          keys:
            - source-v1-{{ .Branch }}-{{ .Revision }}
            - source-v1-{{ .Branch }}-
            - source-v1-

      - checkout

      - save_cache:
          key: source-v1-{{ .Branch }}-{{ .Revision }}
          paths:
            - ".git"

When CircleCI encounters a list of keys, the cache will be restored from the first match. In this example, restore_cache looks for a cache hit from the current Git revision, then for a hit from the current branch, and finally for any cache hit, regardless of branch or revision. If there are multiple matches, the most recently generated cache will be used.

I was able to take down a 30-35s checkout task down to the expected 2-3s. A substantial amount of time saved for your continuous integration!

Submit a form with Puppeteer

Standard

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium. Puppeteer runs headless by default, but can be configured to run a full browser.

A thing you will need to do when using Puppeteer is filling out and submit forms. It’s straightforward to fill fields but sometimes it’s difficult to submit the form. The submit input doesn’t have an id, it’s enclosed in multiple tags, there are multiple buttons (like a search bar in the header). An easy thing to do is to reproduce what you would do naturally: press the Enter button to submit the form.

Here is the code to do this:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto('https://www.google.com/admin');

  await page.type('#username', '[email protected]');
  await page.type('#password', 'password');
  await page.keyboard.press('Enter');

  await page.waitForNavigation();
  console.log('New Page URL:', page.url());
  await browser.close();
})();

L’Arcep, régulateur des télécoms, impose la publication des antennes relais en maintenance

Standard

L’Autorité de régulation des communications électroniques et des postes (Arcep) est une autorité administrative indépendante chargée de réguler les communications électroniques et les postes en France. Depuis juillet 2018, les 4 opérateurs mobiles français sont tenus de publier sur leur site une liste d’antennes relais en maintenance (comprendre qui ne fonctionne pas actuellement).

Cette obligation de publication est introduite par autant de décisions qu’il y a d’opérateurs. Pour Free, c’est la décision 2018-0681 qui stipule la chose dans une section intitulée Obligation de transparence.

Le titulaire est tenu, au plus tard le 1er juillet 2018, de publier et de maintenir à jour sur son site Internet, dans un format électronique ouvert et aisément réutilisable, la liste des stations de base qui ne fournissent pas de service de radiotéléphonie mobile ou de service d’accès mobile à très haut débit pour cause de maintenance ou de panne.

Free Mobile publie cette information sur une page dédiée, permettant la recherche d’une éventuelle panne maintenance par code postal et par le biais d’un fichier CSV (un format ouvert et aisément réutilisable). Chez SFR, il y a une jolie page nommée arceptest.html. Bouygues a choisi de publier ces informations sur un sous-domaine antennesindisponibles (mais au format XLS). Orange publie pour sa part un fichier CSV.

Une solution originale de transparence et de régulation

J’ai plusieurs remarques concernant cette mesure de régulation de l’Arcep. Tout d’abord, je trouve très intéressant et je salue le fait que l’obligation réside dans la publication par l’opérateur lui-même plutôt que par la transmission de l’information au régulateur. Premièrement, on rend ainsi l’information disponible pour d’autres personnes et on responsabilise l’opérateur. Deuxièmement, on évite le cas où le régulateur est en possession de l’information mais ne la diffuse pas.

Des améliorations possibles

Je regrette que la formulation de l’obligation de publication soit trop floue. La formulation dans un format électronique ouvert et aisément réutilisable reprend une formulation classique de la Loi pour une République numérique. L’obligation ne stipule pas un modèle de données, ainsi les informations publiées et les noms de colonnes varient selon les opérateurs. Enfin, le point le plus embêtant est que l’obligation porte sur une publication des indisponibilités à l’instant T sur les antennes relais. Il aurait été intéressant d’avoir un historique pour effectuer des analyses sur la répétition de pannes sur des zones géographiques, des délais de réparation, des pannes à répétition etc. Un tel historique peut être construit, mais c’est à la charge de qui le désire. J’ai fait cet exercice pour Free Mobile en automatisant la récupération des fichiers CSV toutes les heures et je rends disponible les données récoltées.

La démarche open data de la SNCF

Standard

La Société Nationale des Chemins de Fer français (SNCF) est l’entreprise ferroviaire publique française. Le coeur de métier de la SNCF est d’exploiter un réseau ferroviaire et de transporter des passagers. Les chiffres clés sont impressionnants : 15 000 trains commerciaux arpentent le réseau ferré tous les jours, permettant à 4 millions de clients de se déplacer. Souvent décriée pour ses problématiques opérationnelles et sa difficulté à transmettre des informations aux voyageurs, la SNCF s’est notablement améliorée depuis plusieurs années. Cet effort de diffusion, de transparence et d’amélioration continue est visible sur le portail open data de la SNCF data.sncf.com.

Sur ce portail open data, on retrouve des statistiques de régularité au mois et par type de transport (TGV, Intercités, Transilien, TER). Ces données ont un intérêt pour évaluer l’évolution de la qualité de service au fil des années. Je regrette toutefois l’agrégation assez large : au mois / par région / par axe. Les voyageurs sont plus soucieux des régularités aux heures de pointe et durant les jours ouvrés. Espérons que cela évolue vers un découpage par tranches horaires et par jour. Ces jeux de données sont des incontournables étant donné l’activité commerciale de transports de voyageurs de la SNCF.

Toujours sur la régularité mais dans une temporalité différente, la SNCF propose depuis quelques mois un rapport quotidien sur la régularité des différentes lignes et axes sur la journée d’hier. Cette application est nommée Mes trains d’hier. Les faits majeurs sont relatés, expliquant une mauvaise performance. Je regrette la non disponibilité de ces données en open data et l’impossibilité de choisir une date antérieure.

Capture d’écran de l’application Mes trains d’hier

Une thématique souvent absente sur les portails open data mais présente côté SNCF est celle des jeux de données se rapportant aux ressources humaines, aux salariés, mouvements sociaux, congés, accidents du travail. On retrouve ainsi des jeux de données décrivant les nationalités, les genres, les rémunérations, les journées perdues lors de mouvements sociaux. Ce sont des informations que l’on retrouve dans les rapports annuels des grands groupes mais je salue le fait d’en faire des jeux de données à part entière.

Sur la transparence pure, je relève 2 jeux de données très intéressants : les courriers institutionnels entre son équipe dirigeante et les représentants élus de l’État et des collectivités locales et les indicateurs de Responsabilité Sociétale de l’Entreprise pour le groupe SNCF Réseau depuis 2016.

Notons que ces jeux de données ont été diffusés dans un premier temps sous une licence de réutilisation non homologuée. La situation a été rectifiée en juillet 2019, les données sont dorénavant diffusées sous une licence ODbL.

Enfin, je termine cet article avec les jeux de données qui m’amusent le plus :

Cet article est un rapide tour d’horizon des jeux de données disponibles en ligne. En août 2019, plus de 215 jeux de données sont publiés sur ce portail. Les thématiques majeures non abordées dans cet article sont l’infrastructure ferroviaire, les services aux voyageurs, la billetique et les gares.

Rejoindre le secteur public en tant que professionnel du numérique

Standard

Récemment dans le cadre de mon travail j’ai eu la chance d’organiser un sondage à destination des professionnels du numérique qui sont intéressés pour rejoindre le service public à un moment dans le carrière. La fonction publique est souvent décriée par ces professionnels : méthodes waterfall, processus longs, métiers non compris, salaires peu attractifs, peu de télétravail. C’est le prix à payer pour un métier servant l’intérêt général et qui donne du sens à son travail. Ben Balter a écrit à ce sujet un article de blog très pertinent : 19 reasons why technologists don’t want to work at your government agency.

Des réflexions sont menées en interne dans l’administration pour attirer les talents nécessaires. Les anciens professionnels venant du privé rappellent régulièrement les points sur lesquels il faut s’attarder. Ce sondage était l’occasion de montrer que ce sujet d’actualité est traité et que l’administration est à l’écoute des premiers concernés : les professionnels qui veulent rejoindre le service public un jour ou l’autre. Vous pouvez retrouver ce sondage, les principaux résultats de celui-ci et les données brutes des soumissions reçues dans un article de blog sur Etalab.

Using GitHub Actions to run tests for Python packages

Standard

GitHub recently launched GitHub Actions, a way to automate software workflows and to run continuous integration or continuous delivery with a deep integration with the GitHub platform. It’s currently in beta and the general availability is planned for November 2019. Like CircleCI, jobs are free for public repositories, a chance for open source projects. Workflows are expressed in YAML.

GitHub develops some actions you can reuse and you can build yours. GitHub provides suggestions for common workflow needs: running tests on a Node package, pushing a Docker image to Docker hub when creating a tag etc. The Actions Marketplace has an interesting list of actions to help you get started in various tasks: linting, security, publishing, building, notifications, code reviews etc.

I decided to give it a spin with a Python package. My goal was to run unit tests on various Python versions. GitHub Actions has the concept of build matrix, something coming from Travis CI, which allows you to run a job in different environments (OS, Python version, architecture etc.). It makes it a breeze to test your code in various environments, something you could not easily do locally. You can find the YAML code I wrote to install dependencies and run tests on various Python versions.

You can head to the GitHub Actions documentation for a complete tour of the available features.

Writing tests for your static website: Jekyll, Hugo

Standard

Static site generators like Jekyll or Hugo are awesome to quickly publish a website online. Thanks to GitHub and Netlify, you can leverage powerful collaboration tools and free hosting to have a website up and running quickly. You’ll be able to deploy and update your website in minutes without worrying about hosting. This is super powerful.

One thing I’ve not seen a lot for static websites which is present in traditional software is tests: software you write to prove or make sure that your code does what you expect it to do. Sure, static websites have way less code than libraries or backends, but still: you can quickly have tens of posts and hundreds of lines of YAML in data files. It makes sense to write quick tests to ensure things like required keys are present for posts or foreign key consistency in data files. Tests ensure you have a high quality content on your website and can avoid broken layout which you would detect after browsing your static website.

Writing tests for Jekyll

How would you write tests for a Jekyll website? At the end of the day, static websites are composed of data files (usually in YAML) and content files in Markdown. Standard programming languages (Python, Ruby, PHP) can easily parse these files and you can write assertions about the content of them. These tests should be executed after every git push to perform continuous integration. You can use a platform like CircleCI or GitHub Actions to do this.

Jekyll tests code sample

Here is a sample code to run tests on CircleCI for Markdown posts: making sure required keys are there, tags are present and come from a predefined list, images and Twitter usernames have an expected format. These tests are written using Python 3.6 but you can use whatever programming language you like. You can also write tests for data files in YAML. It gets powerful when you write tests combining data files and content files in Markdown.

These tests run in less than 10 seconds on CircleCI after pushing your code and can quickly catch small mistakes. This is a straightforward way to improve the quality of your static website.

My open source contributions: first semester of 2019

Standard

It’s the end of June and I want to take a step back and reflect on my open source contributions since the beginning of the year. I’ve made 770 public commits on GitHub in the first semester.

I’m working at Etalab, the French government administration in charge of data policy and I’m involved in the Public Interest Entrepreneurs program (hiring talents to work in the administration). 95% of the code I work on is open source. This is a huge privilege and I love it. I get to talk about my work, collaborate with people and help other open source enthusiasts.

Main projects

Here are the main projects I’ve been contributing to (in term of commits):

Overall I’ve contributed to more than 50 public repositories.

Data extraction

I extracted this data thanks to Google BigQuery, which provides a public dataset storing GitHub events. This was the best way I found to extract my public activity on GitHub. Unfortunately GitHub doesn’t provide a way to get this through the API (at the people level) or through a personal export. Here is the SQL request used to extract only my public commits:

SELECT
  repo.name,
  count(1) as nb
FROM (
  TABLE_DATE_RANGE([githubarchive:day.], 
  TIMESTAMP('2019-01-01'), 
  TIMESTAMP('2019-06-31')
))
WHERE actor.login = 'AntoineAugusti'
  and type = 'PushEvent'
GROUP BY repo.name

Community

These open source contributions have been a great opportunity to work with others. Open source lets public administrations interact with other administrations, companies, nonprofits and other governments on a daily basis. It’s been a privilege to answer questions, explain how things work and inspire others. Of course I’ve relied on many open source projects and have been helped a lot by other contributors.

Thanks to all the community, see you online for the next semester!

Discovering content caching with NGINX with proxy_cache

Standard

Lately, I’ve had the chance to discover how to leverage the caching of HTTP responses from proxied servers using NGINX and the proxy_cache configuration directives. The web application I’m talking about is dedicated to show sales of properties in France, recently made available in open data. This represents 15 million sales of real estate (houses, flats, lands, forests etc.) in 5 years. The application, realised by Etalab, gets a national press coverage because it’s hosted on an official government domain and was introduced by the Minister of Public Action and Accounts of France.

The web application is composed of a Python backend, talking to a PostgreSQL database with a standard geographical interface with filters and various zoom levels. You can see a demo of the first version of the application in video and browse the code created by Marion Paclot. Regarding traffic, NGINX handles a traffic of 2500 requests/minute during the day with peaks up to 5000-6000 requests/minute, the analytics are available publicly. Knowing people mainly browse their neighbourhood, it’s important to keep areas with a high population density in cache.

The goal was to keep up with this load with a single server of 8 cores and 32 Go of RAM. NGINX delivers this thanks to its proxy cache. We can serve the application with a load average of a 1-3 and an average RAM usage of 3 Go and 8 Go of proxy cache size. You’ll find the commented NGINX configuration below

# Define a cache of up to 10 Go, with files up to 10 Mo. Files that have
# been created more than 2 days ago will be deleted.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=dvf:10m max_size=10g inactive=2d use_temp_path=off;
# Default key example: md5(GETapp.dvf.etalab.gouv.fr/api/mutations/75101/000AI/from=01-01-2014&to=30-06-2018)
proxy_cache_key "$request_method$host$request_uri";

# Rate limiting by IP, up to 50 Mo of storage and limited to 10 requests per second
limit_req_zone $binary_remote_addr zone=hit_per_ip:50m rate=10r/s;

server {
  server_name app.dvf.etalab.gouv.fr;
  root /srv/dvf/static;

  # Serve directly geojson files with a browser cache of 30 days
  location ~ ^/(cadastre|donneesgeo) {
    expires 30d;
    access_log off;
    add_header Cache-Control "public";
  }

  # Cache static files (index.html / *.js) only 30s in the proxy
  # cache and browser cache of 1 minute to be able to deploy
  # quickly changes.
  location / {
    expires 1m;
    add_header Cache-Control "public";

    proxy_cache dvf;
    proxy_cache_valid 200 30s;
    # Change the default query to drop the query parameters. News sites
    # often add query parameters to the index we are not interested in
    # and could bust our cache
    proxy_cache_key "$request_method$host$request_filename";
    include includes/dvf_proxy.conf;
  }

  # The API tells which sales happened for a specific geographic area
  # between 2 dates.
  # This where we need to talk to Python + PostgreSQL. Keep API responses
  # in cache for 1 day and set the browser cache to 12 hours.
  # Allow a burst of up to 50 requests / second, but requests will be
  # queued to respect the max of 10 requests / second.
  location /api {
    expires 12h;
    add_header Cache-Control "public";

    limit_req zone=hit_per_ip burst=50 nodelay;
    limit_req_status 429;

    proxy_cache dvf;
    proxy_cache_valid 200 1d;
    include includes/dvf_proxy.conf;
  }

  listen 443 ssl http2; # managed by Certbot
  ssl_certificate /etc/letsencrypt/live/app.dvf.etalab.gouv.fr/fullchain.pem; # managed by Certbot
  ssl_certificate_key /etc/letsencrypt/live/app.dvf.etalab.gouv.fr/privkey.pem; # managed by Certbot
  include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
  if ($host = app.dvf.etalab.gouv.fr) {
    return 301 https://$host$request_uri;
  } # managed by Certbot

  server_name app.dvf.etalab.gouv.fr;
  listen 80;
  return 404; # managed by Certbot
}

And the include/dvf_proxy.conf file, which proxies requests to Gunicorn, the Python server:

add_header X-Proxy-Cache $upstream_cache_status;
    
add_header X-Frame-Options SAMEORIGIN;
add_header Content-Security-Policy "frame-ancestors 'self'";
add_header X-Content-Security-Policy "frame-ancestors 'self'";

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:8000;