edoxen 0.3.1 → 0.7.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +10 -6
  3. data/Gemfile +4 -0
  4. data/README.adoc +181 -38
  5. data/TODO.meeting-agenda/01-design-decisions.md +132 -0
  6. data/TODO.meeting-agenda/02-lutaml-canonical.md +61 -0
  7. data/TODO.meeting-agenda/03-ruby-models.md +73 -0
  8. data/TODO.meeting-agenda/04-schema.md +34 -0
  9. data/TODO.meeting-agenda/05-fixtures.md +43 -0
  10. data/TODO.meeting-agenda/06-specs.md +52 -0
  11. data/TODO.meeting-agenda/07-cli.md +55 -0
  12. data/TODO.meeting-agenda/08-docs.md +35 -0
  13. data/TODO.meeting-agenda/09-verify-and-release.md +41 -0
  14. data/TODO.meeting-agenda/10-future-enhancements.md +72 -0
  15. data/TODO.meeting-agenda/README.md +30 -0
  16. data/edoxen.gemspec +1 -0
  17. data/lib/edoxen/agenda.rb +26 -0
  18. data/lib/edoxen/agenda_item.rb +17 -0
  19. data/lib/edoxen/attendance.rb +23 -0
  20. data/lib/edoxen/cli.rb +97 -0
  21. data/lib/edoxen/date_range.rb +11 -0
  22. data/lib/edoxen/deadline.rb +10 -0
  23. data/lib/edoxen/enums.rb +31 -0
  24. data/lib/edoxen/host_ref.rb +14 -0
  25. data/lib/edoxen/link_checker.rb +85 -0
  26. data/lib/edoxen/location.rb +15 -0
  27. data/lib/edoxen/meeting.rb +82 -0
  28. data/lib/edoxen/meeting_collection.rb +26 -0
  29. data/lib/edoxen/meeting_collection_metadata.rb +11 -0
  30. data/lib/edoxen/meeting_localization.rb +15 -0
  31. data/lib/edoxen/meeting_relation.rb +12 -0
  32. data/lib/edoxen/minutes.rb +31 -0
  33. data/lib/edoxen/minutes_section.rb +27 -0
  34. data/lib/edoxen/person.rb +14 -0
  35. data/lib/edoxen/reference.rb +13 -0
  36. data/lib/edoxen/reference_data.rb +95 -0
  37. data/lib/edoxen/resolution_metadata.rb +14 -0
  38. data/lib/edoxen/schedule_item.rb +27 -0
  39. data/lib/edoxen/schedule_item_localization.rb +21 -0
  40. data/lib/edoxen/source_url.rb +6 -2
  41. data/lib/edoxen/version.rb +1 -1
  42. data/lib/edoxen/vote_record.rb +25 -0
  43. data/lib/edoxen.rb +23 -0
  44. data/schema/edoxen.yaml +11 -3
  45. data/schema/meeting.yaml +480 -0
  46. metadata +48 -2
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Walks a directory tree of Meeting and ResolutionCollection YAML
5
+ # files and asserts that every cross-document URN link resolves.
6
+ #
7
+ # Invariants enforced:
8
+ #
9
+ # * Every Meeting#urn (if set) is referenced by exactly one
10
+ # ResolutionCollection#metadata.meeting_urn (if the collection
11
+ # exists in the scanned directory).
12
+ # * Every ResolutionCollection#metadata.meeting_urn (if set)
13
+ # resolves to a Meeting whose urn matches.
14
+ #
15
+ # Use from CLI / scripts via the public `check` class method. Returns
16
+ # an array of `LinkError` records (empty = ok).
17
+ #
18
+ # All file IO is read-only. No edits to the YAMLs.
19
+ class LinkChecker
20
+ LinkError = Struct.new(:file, :pointer, :urn, :kind) do
21
+ def message
22
+ "#{file}: #{pointer} references #{urn} (#{kind})"
23
+ end
24
+ end
25
+
26
+ # Walk `dir` (recursive scan of *.yaml and *.yml), classify each
27
+ # file as Meeting or ResolutionCollection, and assert every
28
+ # cross-document URN resolves to a real record.
29
+ #
30
+ # @return [Array<LinkError>] empty when all links resolve.
31
+ def self.check(dir)
32
+ new(dir).check
33
+ end
34
+
35
+ def initialize(dir)
36
+ @dir = dir
37
+ @meetings_by_urn = {} # urn → file
38
+ @collections_by_meeting_urn = {} # meeting_urn → file
39
+ end
40
+
41
+ def check
42
+ require "yaml"
43
+
44
+ Dir.glob(File.join(@dir, "**", "*.{yaml,yml}")).sort.each do |file|
45
+ data = safe_load_yaml(file)
46
+ next unless data.is_a?(Hash)
47
+
48
+ if meeting_shape?(data)
49
+ urn = data["urn"]
50
+ @meetings_by_urn[urn] = file if urn
51
+ elsif collection_shape?(data)
52
+ meeting_urn = data.dig("metadata", "meeting_urn")
53
+ @collections_by_meeting_urn[meeting_urn] = file if meeting_urn
54
+ end
55
+ end
56
+
57
+ errors = []
58
+
59
+ # ResolutionCollection → Meeting
60
+ @collections_by_meeting_urn.each do |meeting_urn, file|
61
+ next if @meetings_by_urn.key?(meeting_urn)
62
+
63
+ errors << LinkError.new(file, "metadata.meeting_urn", meeting_urn, "no matching Meeting")
64
+ end
65
+
66
+ errors
67
+ end
68
+
69
+ private
70
+
71
+ def safe_load_yaml(file)
72
+ YAML.safe_load(File.read(file))
73
+ rescue Psych::SyntaxError, ArgumentError
74
+ nil
75
+ end
76
+
77
+ def meeting_shape?(data)
78
+ data["identifier"].is_a?(Array) && data.key?("type")
79
+ end
80
+
81
+ def collection_shape?(data)
82
+ data["resolutions"].is_a?(Array) || data["metadata"].is_a?(Hash)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Venue geography. Multi-venue meetings (e.g., a plenary week with
5
+ # different hotels) carry one Location per venue.
6
+ class Location < Lutaml::Model::Serializable
7
+ attribute :name, :string
8
+ attribute :address, :string
9
+ attribute :link, :string
10
+ attribute :phone, :string
11
+ attribute :note, :string
12
+ attribute :lat, :float
13
+ attribute :lon, :float
14
+ end
15
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # A single Meeting — the event that produces Resolutions. Carries
5
+ # identity, time, venue, officers, agenda, schedule, deadlines, and
6
+ # URN links to one or more ResolutionCollection documents.
7
+ #
8
+ # Meetings and ResolutionCollections are kept as separate documents
9
+ # and joined by URN (Meeting.resolution_refs ↔
10
+ # ResolutionCollection.metadata.meeting_urn) because they have
11
+ # different lifetimes: agendas exist weeks before a meeting;
12
+ # resolutions only after adoption.
13
+ class Meeting < Lutaml::Model::Serializable
14
+ attribute :identifier, StructuredIdentifier, collection: true
15
+ attribute :urn, :string
16
+ attribute :ordinal, :integer
17
+ attribute :type, :string, values: Enums::MEETING_TYPE
18
+ attribute :status, :string, values: Enums::MEETING_STATUS
19
+ attribute :year, :integer
20
+
21
+ attribute :date_range, DateRange
22
+
23
+ attribute :committee, :string
24
+ attribute :committee_group, :string
25
+
26
+ attribute :venues, Location, collection: true
27
+ attribute :general_area, :string
28
+ attribute :city, :string
29
+ attribute :country_code, :string
30
+ attribute :virtual, :boolean
31
+
32
+ attribute :chair, Person
33
+ attribute :secretary, Person
34
+ attribute :host, :string
35
+ attribute :hosts, HostRef, collection: true
36
+
37
+ attribute :source_urls, SourceUrl, collection: true
38
+ attribute :landing_url, :string
39
+ attribute :registration_url, :string
40
+
41
+ attribute :agenda, Agenda
42
+ attribute :schedule, ScheduleItem, collection: true
43
+ attribute :deadlines, Deadline, collection: true
44
+
45
+ attribute :attendance, Attendance, collection: true
46
+ attribute :vote_records, VoteRecord, collection: true
47
+ attribute :minutes, Minutes, collection: true
48
+
49
+ attribute :localizations, MeetingLocalization, collection: true
50
+ attribute :relations, MeetingRelation, collection: true
51
+ attribute :resolution_refs, :string, collection: true
52
+
53
+ # Lookup by ISO 639-3 language code. Mirrors Resolution#in_language.
54
+ def in_language(code, fallback: false)
55
+ match = localizations&.find { |loc| loc.language_code == code.to_s }
56
+ return match if match
57
+
58
+ fallback ? localizations&.first : nil
59
+ end
60
+
61
+ # English when available, else the first declared localization.
62
+ def primary_localization
63
+ in_language("eng", fallback: true)
64
+ end
65
+
66
+ # Find an agenda item by label. Returns nil when no agenda or no
67
+ # matching label.
68
+ def find_agenda_item(label)
69
+ agenda&.find_item(label)
70
+ end
71
+
72
+ # Resolve the meeting's UN/LOCODE via the canonical `unlocode` gem
73
+ # registry. Returns an `Unlocodes::Entry` (with `#name`, `#country`,
74
+ # `#coordinates`, `#iata`, `#functions`, etc.) or nil when the
75
+ # city field is empty / not in the registry.
76
+ def city_entry
77
+ return nil if city.nil? || city.to_s.empty?
78
+
79
+ Edoxen::ReferenceData.find_unlocode(city)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Top-level container for many Meetings. Parallel to
5
+ # ResolutionCollection.
6
+ class MeetingCollection < Lutaml::Model::Serializable
7
+ attribute :metadata, MeetingCollectionMetadata
8
+ attribute :meetings, Meeting, collection: true
9
+
10
+ # Find a meeting by URN. Returns nil when no meeting matches.
11
+ def find_by_urn(urn)
12
+ meetings&.find { |m| m.urn == urn.to_s }
13
+ end
14
+
15
+ # Find a meeting by StructuredIdentifier components.
16
+ # Returns the first meeting whose any identifier matches both
17
+ # prefix and number.
18
+ def find_by_identifier(prefix:, number:)
19
+ meetings&.find do |meeting|
20
+ meeting.identifier&.any? do |id|
21
+ id.prefix == prefix.to_s && id.number == number.to_s
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Top-level wrapper for many Meetings in a single YAML file. Parallel
5
+ # to ResolutionCollection. The metadata block carries display-level
6
+ # info (title, source); per-meeting identity lives on each Meeting.
7
+ class MeetingCollectionMetadata < Lutaml::Model::Serializable
8
+ attribute :title, :string
9
+ attribute :source, :string
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Per-language content for a Meeting. Mirrors the glossarist
5
+ # pattern used by `Localization` for Resolutions: language-agnostic
6
+ # admin fields live on the parent Meeting; per-language content
7
+ # lives here.
8
+ class MeetingLocalization < Lutaml::Model::Serializable
9
+ attribute :language_code, :string
10
+ attribute :script, :string
11
+ attribute :title, :string
12
+ attribute :general_area, :string
13
+ attribute :practical_info, :string
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Directed link between two meetings, identified by their
5
+ # StructuredIdentifier. Reuses the same relation-type vocabulary
6
+ # pattern as ResolutionRelation.
7
+ class MeetingRelation < Lutaml::Model::Serializable
8
+ attribute :source, StructuredIdentifier
9
+ attribute :destination, StructuredIdentifier
10
+ attribute :type, :string, values: Enums::MEETING_RELATION_TYPE
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # The narrative record of a Meeting — what was said, by whom, in
5
+ # what order, with what outcome. Mirrors
6
+ # edoxen-model/models/minutes.lutaml (P2.8 from
7
+ # TODO.meeting-agenda/10-future-enhancements.md).
8
+ #
9
+ # Distinct from Agenda (the business order drafted before the
10
+ # meeting) and from ResolutionCollection (the formal decisions
11
+ # adopted). The Minutes are the running record.
12
+ #
13
+ # Sections are typically keyed by agenda-item number so consumers
14
+ # can join Minutes ↔ AgendaItem ↔ Resolution by `number`/`label`.
15
+ class Minutes < Lutaml::Model::Serializable
16
+ attribute :identifier, StructuredIdentifier, collection: true
17
+ attribute :urn, :string
18
+ attribute :language_code, :string
19
+ attribute :script, :string
20
+ attribute :source_doc, :string
21
+ attribute :source_pages, :string
22
+ attribute :sections, MinutesSection, collection: true
23
+
24
+ def find_section(number)
25
+ return nil if number.nil?
26
+
27
+ target = number.to_s
28
+ sections&.find { |s| s.number.to_s == target }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # One section of a Meeting's minutes — typically tied to an agenda
5
+ # item by its `number` field. Carries the narrative as Markdown
6
+ # (the format the GLM-OCR pipeline emits) plus optional page range
7
+ # for provenance back to the source PDF.
8
+ class MinutesSection < Lutaml::Model::Serializable
9
+ attribute :number, :string
10
+ attribute :title, :string
11
+ attribute :narrative, :string
12
+ attribute :page_start, :integer
13
+ attribute :page_end, :integer
14
+ attribute :references, Reference, collection: true
15
+
16
+ def in_language(code, fallback: false)
17
+ match = localizations&.find { |loc| loc.language_code == code.to_s }
18
+ return match if match
19
+
20
+ fallback ? localizations&.first : nil
21
+ end
22
+
23
+ def primary_localization
24
+ in_language("eng", fallback: true)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Identity + role + affiliation + contact. Used for meeting officers
5
+ # (chair, secretary, local contact) and host-ref contacts.
6
+ class Person < Lutaml::Model::Serializable
7
+ attribute :name, :string
8
+ attribute :role, :string
9
+ attribute :affiliation, :string
10
+ attribute :email, :string
11
+ attribute :phone, :string
12
+ attribute :orcid, :string
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Generic document reference (used by AgendaItem.references). `kind`
5
+ # is a free-form discriminator (e.g., "standard", "document",
6
+ # "ballot", "resolution"); the schema leaves it open deliberately so
7
+ # new reference kinds need no schema change.
8
+ class Reference < Lutaml::Model::Serializable
9
+ attribute :ref, :string
10
+ attribute :kind, :string
11
+ attribute :title, :string
12
+ end
13
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Built-in reference data for ISO codes used across the Edoxen model:
5
+ # ISO 3166-1 alpha-2 country codes, ISO 639-3 language codes, ISO
6
+ # 15924 script codes, and UN/LOCODEs.
7
+ #
8
+ # UN/LOCODE lookups delegate to the `unlocode` gem (the canonical
9
+ # Ruby registry of the UNECE UN/LOCODE dataset). The frozen
10
+ # ReferenceData::UNLOCODES list stays as a curated offline subset
11
+ # — used by specs and consumers that don't want to load the full
12
+ # ~100k-entry registry.
13
+ #
14
+ # Reference data sources:
15
+ # * https://www.iso.org/iso-3166-country-codes.html
16
+ # * https://iso639-3.sil.org/
17
+ # * https://unicode.org/iso15924/
18
+ # * https://unece.org/trade/cefact/unlocode-code-list-country-and-territory
19
+ #
20
+ # UN/LOCODE format: 2-letter ISO 3166-1 country code + 3-character
21
+ # location alphanumeric (e.g., FRPAR = Paris, France; HKHKG = Hong
22
+ # Kong; THCNM = Chiang Mai). UN-maintained, supersedes the
23
+ # airport-centric IATA city codes edoxen previously used.
24
+ module ReferenceData
25
+ # Hard require the unlocode gem. It's a declared runtime dep, so a
26
+ # clean install of edoxen brings it in. The bundled dataset ships
27
+ # 4 sample entries; full coverage requires `rake unlocodes:fetch`
28
+ # from the unlocode gem.
29
+ require "unlocodes"
30
+
31
+ # ISO 3166-1 alpha-2 — countries ISO/TC 154 / OIML / BIPM operate in.
32
+ # Two-letter uppercase.
33
+ COUNTRY_CODES = %w[
34
+ AE AF AR AT AU BE BG BR BY CA CH CL CN CO CY CZ DE DK EE ES FI FR
35
+ GB GR HK HR HU ID IE IL IN IS IT JP KE KR LK LT LU LV MT MX MY NL NO PH
36
+ NZ PL PT RO RS RU SA SE SG SI SK TH TN TR TW UA US VN ZA
37
+ ].freeze
38
+
39
+ # ISO 639-3 — languages actually used in the edoxen fixtures
40
+ # (eng + fra in OIML, plus the ISO official languages).
41
+ # Three-letter lowercase.
42
+ LANGUAGE_CODES = %w[
43
+ ara chi deu eng fra jpn rus spa zho
44
+ ].freeze
45
+
46
+ # ISO 15924 — scripts. Latn + Cyrl + Hant + Hans cover every script
47
+ # ISO and OIML resolutions are written in.
48
+ SCRIPT_CODES = %w[
49
+ Arab Cyrl Hans Hant Hang Hebr Jpan Kore Latn
50
+ ].freeze
51
+
52
+ # Curated offline subset of UN/LOCODEs — the cities ISO/TC 154 and
53
+ # OIML actually meet in plus the major transit hubs. Used by specs
54
+ # and consumers that don't want to load the full registry. For
55
+ # arbitrary lookups, use {find_unlocode}.
56
+ UNLOCODES = %w[
57
+ AUMEL ATVIE BEBRU BGSOF BRBSB CHGVA CNCAN CNHKG CNSHA CNXSZ COCTG
58
+ CYLCA CZPRG DEBER DEHAM DEFRA DKCPH ESMAD FIHEL FRARC FRLYS FRMRS
59
+ FRPAR GBLON HKHKG HUBUD IDJKT IEDUB IEORK ILTLV INDEL ISREY ITROM
60
+ JPTYO KEMBA KRSEL LKCMB LVRIX LULUX MYKUL NLRTM NOOSL NZAKL PHMNL
61
+ PLWAW PTLIS ROBUH RSBEG SESTO SGSIN SKBTS THBKK THCNM TRIST TWTPE
62
+ UAIEV USMIA USNYC USORL VNSGN ZACPT
63
+ ].freeze
64
+
65
+ # @deprecated Use {UNLOCODES}. Retained for one release to ease
66
+ # migration; will be removed in the next minor.
67
+ CITY_CODES = UNLOCODES
68
+
69
+ class << self
70
+ # Look up a UN/LOCODE entry via the canonical `unlocode` gem.
71
+ # Returns an `Unlocodes::Entry` (with `#name`, `#country`,
72
+ # `#coordinates`, `#iata`, `#functions`, etc.) or nil when the
73
+ # code is not in the registry.
74
+ #
75
+ # Requires the full UN/LOCODE dataset to be loaded. The gem
76
+ # ships a 4-entry sample by default; run `rake unlocodes:fetch`
77
+ # from the unlocode gem to populate the full ~100k-entry
78
+ # registry.
79
+ #
80
+ # @param code [String, #to_s] 5-character UN/LOCODE
81
+ # @return [Unlocodes::Entry, nil]
82
+ def find_unlocode(code)
83
+ Unlocodes.find(code.to_s.upcase)
84
+ end
85
+
86
+ # True when the code resolves to a real UN/LOCODE entry via the
87
+ # canonical registry. Use this when validating user-supplied
88
+ # city codes; use the schema's pattern check for cheap shape
89
+ # validation.
90
+ def unlocode_exists?(code)
91
+ !find_unlocode(code).nil?
92
+ end
93
+ end
94
+ end
95
+ end
@@ -12,5 +12,19 @@ module Edoxen
12
12
  attribute :source_urls, SourceUrl, collection: true
13
13
  attribute :city, :string
14
14
  attribute :country_code, :string
15
+
16
+ # URN back-reference to the Meeting that produced this collection.
17
+ # Join key for the Meeting ↔ ResolutionCollection link. Optional —
18
+ # collections without a parent meeting (e.g., ad-hoc resolution
19
+ # sets) simply omit it.
20
+ attribute :meeting_urn, :string
21
+
22
+ # Resolve the collection's host-city UN/LOCODE via the canonical
23
+ # `unlocode` gem registry. Returns an `Unlocodes::Entry` or nil.
24
+ def city_entry
25
+ return nil if city.nil? || city.to_s.empty?
26
+
27
+ Edoxen::ReferenceData.find_unlocode(city)
28
+ end
15
29
  end
16
30
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # One entry in the meeting timetable. Structural fields (date, time,
5
+ # room) are language-agnostic; per-language content (event name,
6
+ # description) lives in `localizations[]`. Mirrors the glossarist
7
+ # LocalizedConcept pattern.
8
+ class ScheduleItem < Lutaml::Model::Serializable
9
+ attribute :date, :date
10
+ attribute :time, :string
11
+ attribute :event, :string
12
+ attribute :description, :string
13
+ attribute :room, :string
14
+ attribute :localizations, ScheduleItemLocalization, collection: true
15
+
16
+ def in_language(code, fallback: false)
17
+ match = localizations&.find { |loc| loc.language_code == code.to_s }
18
+ return match if match
19
+
20
+ fallback ? localizations&.first : nil
21
+ end
22
+
23
+ def primary_localization
24
+ in_language("eng", fallback: true)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Per-language content for a ScheduleItem. Mirrors the glossarist
5
+ # LocalizedConcept pattern: structural fields (date, time, room) live
6
+ # on the parent ScheduleItem; per-language content (event name,
7
+ # description) lives here.
8
+ class ScheduleItemLocalization < Lutaml::Model::Serializable
9
+ attribute :language_code, :string
10
+ attribute :script, :string
11
+ attribute :event, :string
12
+ attribute :description, :string
13
+
14
+ key_value do
15
+ map "language_code", to: :language_code
16
+ map "script", to: :script
17
+ map "event", to: :event
18
+ map "description", to: :description
19
+ end
20
+ end
21
+ end
@@ -2,11 +2,15 @@
2
2
 
3
3
  module Edoxen
4
4
  # Per-language canonical source URL (e.g. one PDF per language).
5
- # Carries the URL ref, its format, and the ISO 639-3 language_code the URL
6
- # is the canonical source for.
5
+ # Carries the URL ref, its format, the ISO 639-3 language_code the URL
6
+ # is the canonical source for, and an optional `kind` discriminator
7
+ # (agenda_pdf, minutes_pdf, resolutions_pdf, ...) used by the Meeting
8
+ # model. Existing Resolution fixtures without `kind` still parse —
9
+ # the field is optional.
7
10
  class SourceUrl < Lutaml::Model::Serializable
8
11
  attribute :ref, :string
9
12
  attribute :format, :string
10
13
  attribute :language_code, :string
14
+ attribute :kind, :string, values: Enums::SOURCE_URL_KIND
11
15
  end
12
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Edoxen
4
- VERSION = "0.3.1"
4
+ VERSION = "0.7.2"
5
5
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # A single vote on a Resolution, recorded against the meeting at
5
+ # which the vote was taken. Mirrors
6
+ # edoxen-model/models/vote_record.lutaml. Each record carries the
7
+ # person, their affiliation (national body / liaison / committee),
8
+ # and the outcome (affirmative / negative / abstain / absent /
9
+ # not_applicable).
10
+ class VoteRecord < Lutaml::Model::Serializable
11
+ attribute :resolution_ref, :string
12
+ attribute :person, Person
13
+ attribute :affiliation, :string
14
+ attribute :vote, :string, values: Enums::VOTE_TYPE
15
+ attribute :notes, :string
16
+
17
+ key_value do
18
+ map "resolution_ref", to: :resolution_ref
19
+ map "person", to: :person
20
+ map "affiliation", to: :affiliation
21
+ map "vote", to: :vote
22
+ map "notes", to: :notes
23
+ end
24
+ end
25
+ end
data/lib/edoxen.rb CHANGED
@@ -23,6 +23,7 @@ module Edoxen
23
23
  autoload :Error, "edoxen/error"
24
24
  autoload :ValidationError, "edoxen/error"
25
25
  autoload :Enums, "edoxen/enums"
26
+ autoload :ReferenceData, "edoxen/reference_data"
26
27
 
27
28
  # Information-model classes (one per file, one concept per class).
28
29
  # Names mirror ../edoxen-model/models/*.lutaml.
@@ -40,7 +41,29 @@ module Edoxen
40
41
  autoload :ResolutionMetadata, "edoxen/resolution_metadata"
41
42
  autoload :ResolutionCollection, "edoxen/resolution_collection"
42
43
 
44
+ # Meeting / Agenda side. Mirrors edoxen-model/models/meeting*.lutaml.
45
+ autoload :DateRange, "edoxen/date_range"
46
+ autoload :Location, "edoxen/location"
47
+ autoload :Person, "edoxen/person"
48
+ autoload :HostRef, "edoxen/host_ref"
49
+ autoload :ScheduleItemLocalization, "edoxen/schedule_item_localization"
50
+ autoload :ScheduleItem, "edoxen/schedule_item"
51
+ autoload :Deadline, "edoxen/deadline"
52
+ autoload :Reference, "edoxen/reference"
53
+ autoload :AgendaItem, "edoxen/agenda_item"
54
+ autoload :Agenda, "edoxen/agenda"
55
+ autoload :Attendance, "edoxen/attendance"
56
+ autoload :VoteRecord, "edoxen/vote_record"
57
+ autoload :MinutesSection, "edoxen/minutes_section"
58
+ autoload :Minutes, "edoxen/minutes"
59
+ autoload :MeetingRelation, "edoxen/meeting_relation"
60
+ autoload :MeetingLocalization, "edoxen/meeting_localization"
61
+ autoload :Meeting, "edoxen/meeting"
62
+ autoload :MeetingCollectionMetadata, "edoxen/meeting_collection_metadata"
63
+ autoload :MeetingCollection, "edoxen/meeting_collection"
64
+
43
65
  # Services.
44
66
  autoload :SchemaValidator, "edoxen/schema_validator"
67
+ autoload :LinkChecker, "edoxen/link_checker"
45
68
  autoload :Cli, "edoxen/cli"
46
69
  end
data/schema/edoxen.yaml CHANGED
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  $schema: "http://json-schema.org/draft-07/schema#"
3
- $id: "https://github.com/metanorma/edoxen/schema/edoxen.yaml"
3
+ $id: "https://github.com/edoxen/edoxen/schema/edoxen.yaml"
4
4
  title: "Edoxen Resolution Collection Schema"
5
5
  description: |
6
6
  Schema for validating Edoxen ResolutionCollection YAML files.
7
7
 
8
8
  Mirrors the canonical LutaML information model in
9
- https://github.com/metanorma/edoxen-model/tree/main/models .
9
+ https://github.com/edoxen/edoxen-model/tree/main/models .
10
10
 
11
11
  The enum constants declared under `$defs` (ActionType,
12
12
  ConsiderationType, ApprovalType, ApprovalDegree, ResolutionType,
@@ -207,6 +207,10 @@ $defs:
207
207
  language_code:
208
208
  type: string
209
209
  pattern: "^[a-z]{3}$"
210
+ kind:
211
+ type: string
212
+ description: "Optional discriminator (agenda_pdf, minutes_pdf, ...)."
213
+ enum: [agenda_pdf, minutes_pdf, resolutions_pdf, report_pdf, register_url, landing_page]
210
214
 
211
215
  # ====================================================================
212
216
  # Top-level structures.
@@ -285,10 +289,14 @@ $defs:
285
289
  description: "Canonical URLs to the source PDFs in each available language."
286
290
  city:
287
291
  type: string
288
- description: "IATA city code (3-letter, e.g. BER, CPT, PAR) of the host city."
292
+ pattern: "^[A-Z]{2}[A-Z0-9]{3}$"
293
+ description: "UN/LOCODE of the host city (5-char: 2-letter ISO 3166-1 country + 3-char location, e.g. FRPAR, CNSHA, HKHKG). Supersedes the IATA city code."
289
294
  country_code:
290
295
  type: string
291
296
  description: "ISO 3166-1 alpha-2 country code (2-letter, e.g. DE, FR, JP) of the host country."
297
+ meeting_urn:
298
+ type: string
299
+ description: "URN back-reference to the Meeting that produced this collection."
292
300
 
293
301
  # ====================================================================
294
302
  # Enums. Each is referenced by its $ref name above. The schema