decoupage_administratif 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +55 -0
  4. data/CHANGELOG.md +35 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +320 -0
  7. data/Rakefile +14 -0
  8. data/data/communes.json +37640 -0
  9. data/data/departements.json +111 -0
  10. data/data/epci.json +1257 -0
  11. data/data/regions.json +28 -0
  12. data/lib/decoupage_administratif/base_model.rb +60 -0
  13. data/lib/decoupage_administratif/commune.rb +82 -0
  14. data/lib/decoupage_administratif/config.rb +47 -0
  15. data/lib/decoupage_administratif/departement.rb +51 -0
  16. data/lib/decoupage_administratif/epci.rb +61 -0
  17. data/lib/decoupage_administratif/parser.rb +41 -0
  18. data/lib/decoupage_administratif/railtie.rb +11 -0
  19. data/lib/decoupage_administratif/region.rb +50 -0
  20. data/lib/decoupage_administratif/search.rb +181 -0
  21. data/lib/decoupage_administratif/territory_extensions.rb +35 -0
  22. data/lib/decoupage_administratif/territory_strategies.rb +87 -0
  23. data/lib/decoupage_administratif/version.rb +7 -0
  24. data/lib/decoupage_administratif.rb +26 -0
  25. data/lib/tasks/install.rake +59 -0
  26. data/sig/decoupage_administratif/base_model.rbs +7 -0
  27. data/sig/decoupage_administratif/commune.rbs +32 -0
  28. data/sig/decoupage_administratif/departement.rbs +24 -0
  29. data/sig/decoupage_administratif/epci.rbs +23 -0
  30. data/sig/decoupage_administratif/parser.rbs +11 -0
  31. data/sig/decoupage_administratif/region.rbs +21 -0
  32. data/sig/decoupage_administratif/search.rbs +20 -0
  33. data/sig/decoupage_administratif/territory_extensions.rbs +11 -0
  34. data/sig/decoupage_administratif/territory_strategies.rbs +51 -0
  35. data/sig/decoupage_administratif.rbs +4 -0
  36. metadata +96 -0
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DecoupageAdministratif
4
+ module TerritoryStrategies
5
+ class Base
6
+ def initialize(territory)
7
+ @territory = territory
8
+ end
9
+
10
+ def intersects_with_insee_codes?(commune_insee_codes)
11
+ return false if commune_insee_codes.empty?
12
+
13
+ perform_intersection(commune_insee_codes)
14
+ end
15
+
16
+ def insee_codes
17
+ @insee_codes ||= calculate_insee_codes
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :territory
23
+
24
+ def perform_intersection(commune_insee_codes)
25
+ raise NotImplementedError, "Must be implemented by subclass"
26
+ end
27
+
28
+ def calculate_insee_codes
29
+ raise NotImplementedError, "Must be implemented by subclass"
30
+ end
31
+ end
32
+
33
+ class CommuneStrategy < Base
34
+ private
35
+
36
+ def perform_intersection(commune_insee_codes)
37
+ commune_insee_codes.include?(territory.code)
38
+ end
39
+
40
+ def calculate_insee_codes
41
+ [territory.code]
42
+ end
43
+ end
44
+
45
+ class DepartementStrategy < Base
46
+ private
47
+
48
+ def perform_intersection(commune_insee_codes)
49
+ departement_prefix = territory.code.length == 2 ? territory.code : territory.code[0..1]
50
+ commune_insee_codes.any? { |commune_code| commune_code.start_with?(departement_prefix) }
51
+ end
52
+
53
+ def calculate_insee_codes
54
+ territory.communes.map(&:code)
55
+ end
56
+ end
57
+
58
+ class RegionStrategy < Base
59
+ private
60
+
61
+ def perform_intersection(commune_insee_codes)
62
+ departement_codes = territory.departements.map(&:code)
63
+ commune_insee_codes.any? do |commune_code|
64
+ dept_code = commune_code.length >= 3 && commune_code[0..1].to_i >= 96 ? commune_code[0..2] : commune_code[0..1]
65
+ departement_codes.include?(dept_code)
66
+ end
67
+ end
68
+
69
+ def calculate_insee_codes
70
+ territory.communes.map(&:code)
71
+ end
72
+ end
73
+
74
+ class EpciStrategy < Base
75
+ private
76
+
77
+ def perform_intersection(commune_insee_codes)
78
+ epci_commune_codes = territory.membres.map { |membre| membre[:code] || membre["code"] }
79
+ (epci_commune_codes & commune_insee_codes).any?
80
+ end
81
+
82
+ def calculate_insee_codes
83
+ territory.membres.map { |membre| membre[:code] || membre["code"] }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DecoupageAdministratif
4
+ VERSION = "0.1.0"
5
+ DATA_VERSION = "5.2.0"
6
+ DATA_SOURCE = "@etalab/decoupage-administratif"
7
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "decoupage_administratif/version"
4
+ require_relative "decoupage_administratif/config"
5
+ require_relative "decoupage_administratif/base_model"
6
+ require_relative "decoupage_administratif/territory_strategies"
7
+ require_relative "decoupage_administratif/territory_extensions"
8
+ require_relative "decoupage_administratif/parser"
9
+ require_relative "decoupage_administratif/commune"
10
+ require_relative "decoupage_administratif/departement"
11
+ require_relative "decoupage_administratif/region"
12
+ require_relative "decoupage_administratif/epci"
13
+ require_relative "decoupage_administratif/search"
14
+
15
+ require 'decoupage_administratif/railtie' if defined?(Rails)
16
+
17
+ module DecoupageAdministratif
18
+ class Error < StandardError; end
19
+ class NotFoundError < Error; end
20
+
21
+ # Returns information about the embedded data version
22
+ # @return [String] formatted string with data version information
23
+ def self.data_info
24
+ "Data version: #{DATA_VERSION} (from #{DATA_SOURCE})"
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require_relative '../decoupage_administratif/config'
7
+
8
+ def download_file(url, destination)
9
+ puts "Downloading from #{url}..."
10
+
11
+ uri = URI(url)
12
+ response = Net::HTTP.get_response(uri)
13
+
14
+ raise "Download failed with status: #{response.code} #{response.message}" unless response.is_a?(Net::HTTPSuccess)
15
+
16
+ File.binwrite(destination, response.body)
17
+ puts "Successfully downloaded"
18
+
19
+ # Validate JSON
20
+ begin
21
+ JSON.parse(File.read(destination))
22
+ puts "JSON validation successful"
23
+ rescue JSON::ParserError => e
24
+ puts "Error: Invalid JSON file downloaded"
25
+ puts e.message
26
+ FileUtils.rm(destination)
27
+ raise "Download failed: Invalid JSON"
28
+ end
29
+ end
30
+
31
+ namespace :decoupage_administratif do
32
+ desc 'Update files'
33
+ task :update do
34
+ collection = %w[communes departements regions epci]
35
+ data_dir = DecoupageAdministratif::Config.data_directory
36
+ FileUtils.mkdir_p(data_dir)
37
+
38
+ collection.each do |item|
39
+ file = File.join(data_dir, "#{item}.json")
40
+ url = "https://unpkg.com/@etalab/decoupage-administratif@4.0.0/data/#{item}.json"
41
+
42
+ download_file(url, file)
43
+
44
+ puts "Update completed successfully!"
45
+ rescue StandardError => e
46
+ puts "Error during update:"
47
+ puts e.message
48
+ puts e.backtrace
49
+ exit 1
50
+ end
51
+ end
52
+
53
+ desc 'Download files'
54
+ task :install do
55
+ data_dir = DecoupageAdministratif::Config.data_directory
56
+ FileUtils.rm_rf(data_dir) if File.directory?(data_dir)
57
+ Rake::Task['decoupage_administratif:update'].invoke
58
+ end
59
+ end
@@ -0,0 +1,7 @@
1
+ module DecoupageAdministratif
2
+ module BaseModel
3
+ def find: (String) -> untyped
4
+ def find_by: (Hash[Symbol, untyped]) -> untyped
5
+ def where: (Hash[Symbol, untyped]) -> Array[untyped]
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ module DecoupageAdministratif
2
+ extend BaseModel
3
+ class Commune
4
+ extend BaseModel
5
+ include TerritoryExtensions
6
+ attr_reader code: String
7
+ attr_reader nom: String
8
+ attr_reader zone: String
9
+ attr_reader region_code: String
10
+ attr_reader departement_code: String
11
+ attr_reader commune_type: String
12
+
13
+ def initialize: (
14
+ code: String,
15
+ nom: String,
16
+ zone: String,
17
+ region_code: String,
18
+ departement_code: String,
19
+ commune_type: Symbol
20
+ ) -> void
21
+
22
+ def self.all: () -> Array[Commune]
23
+
24
+ def self.communes_actuelles: () -> Array[Commune]
25
+
26
+ def region: () -> Region
27
+
28
+ def departement: () -> Departement
29
+
30
+ def epci: () -> Epci?
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module DecoupageAdministratif
2
+ class Departement
3
+ extend BaseModel
4
+ include TerritoryExtensions
5
+ attr_reader code: String
6
+ attr_reader nom: String
7
+ attr_reader zone: String
8
+ attr_reader code_region: String
9
+
10
+ def initialize: (
11
+ code: String,
12
+ nom: String,
13
+ zone: String,
14
+ code_region: String
15
+ ) -> void
16
+
17
+
18
+ def self.all: () -> Array[Departement]
19
+
20
+ def communes: () -> Array[Commune]
21
+
22
+ def region: () -> Region
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module DecoupageAdministratif
2
+ class Epci
3
+ extend BaseModel
4
+ include TerritoryExtensions
5
+ attr_reader code: String
6
+ attr_reader nom: String
7
+ attr_reader membres: Array[{ code: String, nom: String }]
8
+
9
+ def initialize: (
10
+ code: String,
11
+ nom: String,
12
+ membres: Array[{ code: String, nom: String }]
13
+ ) -> void
14
+
15
+ def self.all: () -> Array[Epci]
16
+
17
+ def self.search_by_communes_codes: (Array[String]) -> Array[Epci]
18
+
19
+ def communes: () -> Array[Commune]
20
+
21
+ def regions: () -> Array[Region]
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ module DecoupageAdministratif
2
+ class Parser
3
+ attr_reader data: Array[Hash[String, untyped]]
4
+
5
+ def initialize: (String) -> void
6
+
7
+ private
8
+
9
+ def load_data: () -> void
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module DecoupageAdministratif
2
+ class Region
3
+ extend BaseModel
4
+ include TerritoryExtensions
5
+ attr_reader code: String
6
+ attr_reader nom: String
7
+ attr_reader zone: String
8
+
9
+ def initialize: (
10
+ code: String,
11
+ nom: String,
12
+ zone: String,
13
+ ) -> void
14
+
15
+ def self.all: () -> Array[Region]
16
+
17
+ def communes: () -> Array[Commune]
18
+
19
+ def departements: () -> Array[Departement]
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module DecoupageAdministratif
2
+ class Search
3
+ attr_reader codes: Array[String] | nil
4
+ attr_reader regions: Array[Region] | nil
5
+ attr_reader departements: Array[Departement] | nil
6
+ attr_reader epcis: Array[Epci] | nil
7
+ attr_reader communes: Array[Commune] | nil
8
+
9
+ def by_insee_codes: () -> Hash[Symbol, Array[Region] | Array[Departement] | Array[Epci] | Array[Commune]]
10
+
11
+ private
12
+
13
+ def find_communes_by_codes: () -> Hash[Departement, Array[Commune]]
14
+
15
+ def search_for_communes: () -> void
16
+ def search_for_departements: () -> void
17
+ def search_for_epcis: () -> void
18
+ def search_for_region: () -> void
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ module DecoupageAdministratif
2
+ module TerritoryExtensions
3
+ def territory_intersects_with_insee_codes?: (Array[String] commune_insee_codes) -> bool
4
+
5
+ def territory_insee_codes: () -> Array[String]
6
+
7
+ private
8
+
9
+ def territory_strategy: () -> TerritoryStrategies::Base
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ module DecoupageAdministratif
2
+ module TerritoryStrategies
3
+ class Base
4
+ def initialize: (untyped territory) -> void
5
+
6
+ def intersects_with_insee_codes?: (Array[String] commune_insee_codes) -> bool
7
+
8
+ def insee_codes: () -> Array[String]
9
+
10
+ private
11
+
12
+ attr_reader territory: untyped
13
+
14
+ def perform_intersection: (Array[String] commune_insee_codes) -> bool
15
+
16
+ def calculate_insee_codes: () -> Array[String]
17
+ end
18
+
19
+ class CommuneStrategy < Base
20
+ private
21
+
22
+ def perform_intersection: (Array[String] commune_insee_codes) -> bool
23
+
24
+ def calculate_insee_codes: () -> Array[String]
25
+ end
26
+
27
+ class DepartementStrategy < Base
28
+ private
29
+
30
+ def perform_intersection: (Array[String] commune_insee_codes) -> bool
31
+
32
+ def calculate_insee_codes: () -> Array[String]
33
+ end
34
+
35
+ class RegionStrategy < Base
36
+ private
37
+
38
+ def perform_intersection: (Array[String] commune_insee_codes) -> bool
39
+
40
+ def calculate_insee_codes: () -> Array[String]
41
+ end
42
+
43
+ class EpciStrategy < Base
44
+ private
45
+
46
+ def perform_intersection: (Array[String] commune_insee_codes) -> bool
47
+
48
+ def calculate_insee_codes: () -> Array[String]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ module DecoupageAdministratif
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: decoupage_administratif
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lucien Mollard
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-08-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Ruby gem for French administrative divisions (découpage administratif
28
+ français). Access data about all French communes (municipalities), départements,
29
+ régions, and EPCIs (intercommunalités). Official data from @etalab/decoupage-administratif.
30
+ email:
31
+ - lucien.mollard@beta.gouv.fr
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - CHANGELOG.md
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - data/communes.json
43
+ - data/departements.json
44
+ - data/epci.json
45
+ - data/regions.json
46
+ - lib/decoupage_administratif.rb
47
+ - lib/decoupage_administratif/base_model.rb
48
+ - lib/decoupage_administratif/commune.rb
49
+ - lib/decoupage_administratif/config.rb
50
+ - lib/decoupage_administratif/departement.rb
51
+ - lib/decoupage_administratif/epci.rb
52
+ - lib/decoupage_administratif/parser.rb
53
+ - lib/decoupage_administratif/railtie.rb
54
+ - lib/decoupage_administratif/region.rb
55
+ - lib/decoupage_administratif/search.rb
56
+ - lib/decoupage_administratif/territory_extensions.rb
57
+ - lib/decoupage_administratif/territory_strategies.rb
58
+ - lib/decoupage_administratif/version.rb
59
+ - lib/tasks/install.rake
60
+ - sig/decoupage_administratif.rbs
61
+ - sig/decoupage_administratif/base_model.rbs
62
+ - sig/decoupage_administratif/commune.rbs
63
+ - sig/decoupage_administratif/departement.rbs
64
+ - sig/decoupage_administratif/epci.rbs
65
+ - sig/decoupage_administratif/parser.rbs
66
+ - sig/decoupage_administratif/region.rbs
67
+ - sig/decoupage_administratif/search.rbs
68
+ - sig/decoupage_administratif/territory_extensions.rbs
69
+ - sig/decoupage_administratif/territory_strategies.rbs
70
+ homepage: https://github.com/betagouv/decoupage-administratif-gem
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ homepage_uri: https://github.com/betagouv/decoupage-administratif-gem
75
+ changelog_uri: https://github.com/betagouv/decoupage-administratif-gem/blob/main/CHANGELOG.md
76
+ rubygems_mfa_required: 'true'
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 3.0.0
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 3.5.11
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: French administrative divisions - Découpage administratif français
96
+ test_files: []