Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registre d'arrêts : première ébauche #4393

Merged
merged 23 commits into from
Jan 20, 2025
Merged

Conversation

ptitfred
Copy link
Contributor

@ptitfred ptitfred commented Dec 17, 2024

Voir l'epic #4354.

Formats supportés :

  • GTFS
  • NeTEx

Résultat : fichier csv sans déduplication (les ids sont ceux issus des ressources) ni rapprochement géographique.

Manquent des tests unitaires.

@ptitfred ptitfred force-pushed the registre-arrets/premier-modele branch 5 times, most recently from 2acbef3 to 274dd93 Compare December 19, 2024 21:58
@ptitfred ptitfred force-pushed the registre-arrets/premier-modele branch from 274dd93 to 604202c Compare December 20, 2024 14:38
Copy link
Contributor

@thbar thbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'ai fait une première passe rapide sur la PR (en mode draft mais comme vu avec @ptitfred ça n'a pas bougé depuis quelques temps).

Notes de relecture

(ça pourra servir au reste de @etalab/transport-tech qui lira sûrement cette PR à un moment).

main_id,display_name,data_source_id,data_source_format,parent_id,latitude,longitude,projection,stop_type
main:FR:52121:StopPlace:genARCOM@CHTDEG:CHT,De Gaulle,PAN:resource:80411,netex,,48.073291,5.1465855,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTOUDI:CHT,Oudinot,PAN:resource:80411,netex,,48.0784048,5.1455719,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTQUEL:CHT,Quellemele,PAN:resource:80411,netex,,48.07965605,5.14367025,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTBROT:CHT,Brottes,PAN:resource:80411,netex,,48.0821128,5.1281127,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTGROU:CHT,Groupama,PAN:resource:80411,netex,,48.0855051,5.13067385,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTTHBIS:CHT,THOMAS,PAN:resource:80411,netex,,48.0818889,5.135882,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTPAQU:CHT,Paquerettes,PAN:resource:80411,netex,,48.08639315,5.13853285,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTFARA:CHT,Faraday,PAN:resource:80411,netex,,48.08933075,5.13727315,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTROCH:CHT,Rochotte,PAN:resource:80411,netex,,48.0917009,5.1381297,utm_wgs84,stop

Points de review

  • Je vois que ça a fini en erreur qui stoppe le processing, je me demande si on peut l'intercepter.
[debug] extract_from_archive Elixir.Transport.Registry.GTFS PAN:resource:82277 /var/folders/tl/1p_8qmn13cbgh_539g1yxyyc0000gn/T/db8c162d-7fa4-4cc8-bffb-d791b569bf61.dat
[debug] Valid Zip archive
** (NimbleCSV.ParseError) unexpected escape character " in "\"MAISON-PONTHIEU - 40\",,50.204352,2.039905,,,,,,0\r\n"
    (nimble_csv 1.2.0) lib/nimble_csv.ex:583: NimbleCSV.RFC4180.escape/6
    (nimble_csv 1.2.0) lib/nimble_csv.ex:453: anonymous fn/4 in NimbleCSV.RFC4180.parse_stream/2
    (elixir 1.16.2) lib/stream.ex:990: Stream.do_transform_user/6
    (elixir 1.16.2) lib/stream.ex:943: Stream.do_transform/5
    (elixir 1.16.2) lib/enum.ex:4396: Enum.reverse/1
    (elixir 1.16.2) lib/enum.ex:3728: Enum.to_list/1
    (transport 0.0.1) lib/registry/gtfs.ex:30: Transport.Registry.GTFS.extract_from_archive/2
    (elixir 1.16.2) lib/stream.ex:613: anonymous fn/4 in Stream.map/2
    (elixir 1.16.2) lib/enum.ex:4839: Enumerable.List.reduce/3
    (elixir 1.16.2) lib/stream.ex:1027: Stream.do_transform_inner_list/7
    (elixir 1.16.2) lib/stream.ex:1828: Enumerable.Stream.do_each/4
    (elixir 1.16.2) lib/stream.ex:943: Stream.do_transform/5
    (elixir 1.16.2) lib/stream.ex:1828: Enumerable.Stream.do_each/4
    (elixir 1.16.2) lib/stream.ex:585: Stream.do_into/4
    (elixir 1.16.2) lib/stream.ex:690: Stream.run/1
    scripts/registre-arrets.exs:1: (file)
  • J'ai déployé sur prochainement pour aller tester
  • Je trouverais ça plus pratique d'avoir en identifiant de la ressource, le UUID datagouv, que notre identifiant "entier" à nous @ptitfred - ça m'a régulièrement évité de passer par un script / indirection pour retrouver le UUID derrière

Beau boulot otherwise, on en reparle cet après-midi !


case value do
nil -> default_value
"" -> default_value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Est-ce qu'il y a un trim en amont ? J'imagine qu'on pourrait dans certains cas tomber sur des valeurs comme " ").

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transform the stream outputed by Unzip to a stream of maps, each map
corresponding to a row from the CSV.
"""
def to_stream_of_maps(file_stream) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je me suis demandé si file_stream "is a" %File.Stream{}.

Avec l'arrivée de Elixir 1.18+ et du typage, j'ai l'impression que mettre les types de structs en paramètre va être une bonne idée.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est un enumerable de iodata (pas sûr du type en elixir).

# transform the stream to a stream of maps %{column_name1: value1, ...}
|> Stream.transform([], fn r, acc ->
if acc == [] do
{%{}, r |> Enum.map(fn h -> h |> String.replace_prefix("\uFEFF", "") end)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je ne sais pas si tu as vu qu'on peut trimmer le BOM directement à l'ouverture du stream:

https://hexdocs.pm/elixir/1.18.1/File.html#stream!/3-byte-order-marks-and-read-offset

File.stream!("./test/test.txt", [:trim_bom, encoding: :utf8])

Mais peut-être que tu as déjà vu et que ce n'est pas pratique etc...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non ; j'ai repris naïvement du code existant.

def error(message), do: {:error, message}

@spec cat_results(Stream.t(t(term()))) :: Stream.t(term())
def cat_results(enumerable), do: Stream.flat_map(enumerable, &keep_ok/1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le nommage m'a un peu pris de court : en voyant cat je crois que j'avais associé ça à une opération sans side-effect (comme cat dans le shell), mais en pratique ça reformatte la donnée.

Copy link
Contributor Author

@ptitfred ptitfred Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je plaide coupable d'avoir repris la nomenclature d'un autre langage :

  • Data.Maybe.catMaybes :: [Maybe a] -> [a]
  • Data.Maybe.mapMaybe :: (a -> Maybe b) -> [a] -> [b]

J'avoue ne pas savoir pourquoi ce préfixe "cat" a été utilisé là-bas. Je suis ouvert aux suggestions.

require Logger

@spec execute(output_file :: Path.t(), list()) :: :ok
def execute(output_file, opts \\ []) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vu que c'est l'entry point du script, quand ça sera fini fini j'ajouterais bien une doc sur la méthode.

@ptitfred ptitfred marked this pull request as ready for review January 14, 2025 11:02
@ptitfred ptitfred requested a review from a team as a code owner January 14, 2025 11:02
@ptitfred
Copy link
Contributor Author

Je vois que ça a fini en erreur qui stoppe le processing, je me demande si on peut l'intercepter.

J'attrape l'erreur. C'est pas forcément la meilleure solution mais ça évite de saborder le reste de l'export.

@ptitfred
Copy link
Contributor Author

Je trouverais ça plus pratique d'avoir en identifiant de la ressource, le UUID datagouv, que notre identifiant "entier" à nous @ptitfred - ça m'a régulièrement évité de passer par un script / indirection pour retrouver le UUID derrière

J'ai fait. A noter qu'avec le resource_id on a la même traçabilité (voire même meilleure avec le resource_history_id). Peut-être est-ce moins pratique pour un tiers ?

@ptitfred ptitfred requested a review from thbar January 15, 2025 16:29
@thbar
Copy link
Contributor

thbar commented Jan 16, 2025

J'ai fait. A noter qu'avec le resource_id on a la même traçabilité (voire même meilleure avec le resource_history_id). Peut-être est-ce moins pratique pour un tiers ?

Merci ! Oui c'est moins pratique, avec l'identifiant data gouv on peut plus facilement récupérer la ressource elle-même via l'API data gouv. Un deuxième argument + secondaire, c'est l'homogénéité avec d'autres consolidations, où dans certains cas on n'a même pas de resource_id PAN du tout (ex: IRVE).

@thbar
Copy link
Contributor

thbar commented Jan 16, 2025

@ptitfred merci pour les updates !

Comme vu en DM, ça lève une autre erreur à présent, liée je pense à une limite en file descriptor sur Mac (mais ça serait très probablement idem ailleurs avec la même limite). C'est intéressant de l'avoir vu en local, car on a un "long running process" où ça leakerait en production aussi.

[debug] Valid Zip archive
[debug] extract_from_archive Elixir.Transport.Registry.GTFS datagouv:resource:61277099-cde9-457c-919d-a1b7b20fe992 /var/folders/hz/7zpbnj551z5169215hq24gy40000gn/T/f4bf0ed9-70a0-480e-ac55-ad420a22e25f.dat
[debug] Valid Zip archive
[debug] extract_from_archive Elixir.Transport.Registry.GTFS datagouv:resource:f6135ec3-0969-4ba1-bf35-69dfd75799cf /var/folders/hz/7zpbnj551z5169215hq24gy40000gn/T/93290fec-95aa-488b-a2aa-b92ae8e3a211.dat
** (MatchError) no match of right hand side value: {:error, :emfile}
    (unzip 0.12.0) lib/unzip/local_file.ex:11: Unzip.LocalFile.open/1
    (transport 0.0.1) lib/registry/gtfs.ex:69: Transport.Registry.GTFS.file_stream/1
    (transport 0.0.1) lib/registry/gtfs.ex:19: Transport.Registry.GTFS.extract_from_archive/2
    (elixir 1.16.2) lib/stream.ex:613: anonymous fn/4 in Stream.map/2
    (elixir 1.16.2) lib/enum.ex:4839: Enumerable.List.reduce/3
    (elixir 1.16.2) lib/stream.ex:1027: Stream.do_transform_inner_list/7
    (elixir 1.16.2) lib/stream.ex:1828: Enumerable.Stream.do_each/4
    (elixir 1.16.2) lib/stream.ex:943: Stream.do_transform/5
    (elixir 1.16.2) lib/stream.ex:1828: Enumerable.Stream.do_each/4
    (elixir 1.16.2) lib/stream.ex:585: Stream.do_into/4
    (elixir 1.16.2) lib/stream.ex:690: Stream.run/1
    scripts/registre-arrets.exs:1: (file)
    (elixir 1.16.2) lib/code.ex:1489: Code.require_file/2
    (mix 1.16.2) lib/mix/tasks/run.ex:146: Mix.Tasks.Run.run/5
[os_mon] memory supervisor port (memsup): Erlang has closed

Je pense directement à quelque chose d'analogue à:

avec un leak de fichiers ouverts pour la lecture des GTFS, vu les logs.

Copy link
Contributor

@thbar thbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sur la machine où ça plantait, tes derniers changements @ptitfred font que ça va au bout ! Plus d'erreur "emfile" en bout de script.

En relisant le code je pense qu'il reste des cas où ça va leaker et je propose un refacto pour supprimer les leaks restants (approche utilisée sur le parseur NeTEx).

Extract stops from GTFS ressource.
"""
def extract_from_archive(data_source_id, archive) do
case file_stream(archive) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le nouveau code va traiter la grande majorité des leaks, mais il va en rester avec cette nouvelle version à mon avis. Le cas {:error, ...} au minimum va leaker, et notamment dans les cas où l'archive peut être ouverte avec succès, mais sa lecture zip technique ici échoue pour X raison:

https://github.com/etalab/transport-site/pull/4393/files#diff-30981bbcdbac682bd45a739edc4dc5c9488adb061bf6b9710a5b36ea5bc1a906R70-R85

Le point pernicieux c'est que ça sera dur à constater (beaucoup moins visible) sur la production, mais ça finira par se produire et redémarrer toute l'application (qui aura épuisé les file descriptors disponibles), de façon régulière.

Je te propose @ptitfred d'utiliser l'approche (voire, le code même, il est pas spécifique à NeTEx en pratique) ici, qui fait un wrapping systématique avec after dans 100% des cas:

def with_zip_file_handle(zip_file_name, cb) do
zip_file = Unzip.LocalFile.open(zip_file_name)
try do
{:ok, unzip} = Unzip.new(zip_file)
cb.(unzip)
after
Unzip.LocalFile.close(zip_file)
end
end

Pas sûr que mon explication soit super claire, hésite pas si besoin !

Copy link
Contributor Author

@ptitfred ptitfred Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je voulais faire ça au début mais on doit attendre que le fichier soit utilisé dans le prochain itérateur.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'ai poussé le close en cas d'erreur.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On en a discuté en point dév, sounds good !

@ptitfred ptitfred force-pushed the registre-arrets/premier-modele branch from e187dbf to d5cd80a Compare January 20, 2025 12:11
Copy link
Contributor

@thbar thbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On est bon !

Note pour @etalab/transport-tech : cette PR crée un fichier mais le code n'est pas invoqué en production. On le déploie ce qui permettra d'aller tester sur la production en console, et on va aussi travailler sur un job qui va "pousser" ce fichier à un endroit déterminé sur S3 (et qui poussera d'ailleurs aussi les artefacts produits par #4397.

@ptitfred ptitfred added this pull request to the merge queue Jan 20, 2025
Merged via the queue into master with commit 7783db7 Jan 20, 2025
4 checks passed
@ptitfred ptitfred deleted the registre-arrets/premier-modele branch January 20, 2025 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants