edoxen 0.7.2 → 2.1.1

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +17 -1
  3. data/.rubocop_todo.yml +1 -1
  4. data/CHANGELOG.md +246 -0
  5. data/README.adoc +308 -153
  6. data/edoxen.gemspec +10 -10
  7. data/lib/edoxen/action.rb +1 -1
  8. data/lib/edoxen/agenda.rb +4 -7
  9. data/lib/edoxen/agenda_item.rb +10 -3
  10. data/lib/edoxen/approval.rb +1 -1
  11. data/lib/edoxen/attendance.rb +16 -10
  12. data/lib/edoxen/body_vocabulary_entry.rb +24 -0
  13. data/lib/edoxen/body_vocabulary_host.rb +29 -0
  14. data/lib/edoxen/cli.rb +117 -120
  15. data/lib/edoxen/component_localization.rb +11 -0
  16. data/lib/edoxen/consideration.rb +2 -2
  17. data/lib/edoxen/contact.rb +24 -0
  18. data/lib/edoxen/contact_identifier.rb +13 -0
  19. data/lib/edoxen/contact_method.rb +16 -0
  20. data/lib/edoxen/decision.rb +69 -0
  21. data/lib/edoxen/decision_collection.rb +10 -0
  22. data/lib/edoxen/decision_date.rb +9 -0
  23. data/lib/edoxen/{resolution_metadata.rb → decision_metadata.rb} +5 -6
  24. data/lib/edoxen/decision_relation.rb +11 -0
  25. data/lib/edoxen/entity_ref.rb +74 -0
  26. data/lib/edoxen/enums.rb +117 -9
  27. data/lib/edoxen/error.rb +1 -1
  28. data/lib/edoxen/extension_attribute.rb +78 -0
  29. data/lib/edoxen/host_ref.rb +1 -1
  30. data/lib/edoxen/link_checker.rb +8 -8
  31. data/lib/edoxen/localization.rb +2 -2
  32. data/lib/edoxen/localization_host.rb +23 -0
  33. data/lib/edoxen/meeting.rb +63 -39
  34. data/lib/edoxen/meeting_collection.rb +1 -2
  35. data/lib/edoxen/meeting_collection_metadata.rb +3 -1
  36. data/lib/edoxen/meeting_component.rb +41 -0
  37. data/lib/edoxen/meeting_extension.rb +25 -0
  38. data/lib/edoxen/meeting_identifier.rb +1 -1
  39. data/lib/edoxen/meeting_localization.rb +6 -1
  40. data/lib/edoxen/meeting_series.rb +21 -0
  41. data/lib/edoxen/minutes.rb +2 -2
  42. data/lib/edoxen/minutes_section.rb +8 -11
  43. data/lib/edoxen/motion.rb +51 -0
  44. data/lib/edoxen/name.rb +29 -0
  45. data/lib/edoxen/officer.rb +19 -0
  46. data/lib/edoxen/officers_host.rb +19 -0
  47. data/lib/edoxen/person.rb +5 -9
  48. data/lib/edoxen/physical_venue.rb +15 -0
  49. data/lib/edoxen/recurrence.rb +27 -0
  50. data/lib/edoxen/recurrence_by_day.rb +10 -0
  51. data/lib/edoxen/reference_data.rb +35 -41
  52. data/lib/edoxen/schema_validator.rb +2 -3
  53. data/lib/edoxen/source_url.rb +3 -3
  54. data/lib/edoxen/structured_identifier.rb +2 -2
  55. data/lib/edoxen/topic.rb +37 -0
  56. data/lib/edoxen/topic_asset.rb +14 -0
  57. data/lib/edoxen/topic_document.rb +16 -0
  58. data/lib/edoxen/venue.rb +80 -0
  59. data/lib/edoxen/venue_validator.rb +56 -0
  60. data/lib/edoxen/version.rb +1 -1
  61. data/lib/edoxen/virtual_venue.rb +15 -0
  62. data/lib/edoxen/vote_record.rb +17 -13
  63. data/lib/edoxen/voting.rb +45 -0
  64. data/lib/edoxen/voting_counts.rb +23 -0
  65. data/lib/edoxen.rb +40 -13
  66. data/schema/edoxen.yaml +792 -145
  67. data/schema/meeting.yaml +600 -142
  68. data/sig/edoxen.rbs +581 -1
  69. metadata +57 -20
  70. data/lib/edoxen/_metadata.rb.deprecated +0 -57
  71. data/lib/edoxen/location.rb +0 -15
  72. data/lib/edoxen/resolution.rb +0 -43
  73. data/lib/edoxen/resolution_collection.rb +0 -10
  74. data/lib/edoxen/resolution_date.rb +0 -11
  75. data/lib/edoxen/resolution_relation.rb +0 -11
  76. data/lib/edoxen/resolution_set.rb +0 -17
  77. data/lib/edoxen/schedule_item.rb +0 -27
  78. data/lib/edoxen/schedule_item_localization.rb +0 -21
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Venue — polymorphic by discriminator, flat on the wire.
5
+ #
6
+ # The `kind` field discriminates between physical and virtual venues.
7
+ # All possible fields (physical-specific and virtual-specific) live on
8
+ # this one class as optional attributes — only the ones matching the
9
+ # `kind` are populated. This avoids lutaml-model's polymorphic
10
+ # recursion pitfalls while keeping the wire format readable.
11
+ #
12
+ # The PhysicalVenue and VirtualVenue subclasses exist for type-checking
13
+ # and for carrying type-specific helpers (e.g. `#unlocode_entry`).
14
+ # Construct them programmatically; the YAML wire format always uses
15
+ # the flat shape via Venue.
16
+ #
17
+ # Replaces v0.x `Location` (physical-only) and `Meeting.virtual: Boolean`
18
+ # (insufficient — Zoom needs URL+passcode+dial-in).
19
+ class Venue < Lutaml::Model::Serializable
20
+ attribute :kind, :string, values: Enums::VENUE_KIND
21
+ attribute :name, :string
22
+ attribute :label, :string
23
+ attribute :description, :string
24
+ attribute :capacity, :integer
25
+ attribute :url, :string
26
+ attribute :contact_methods, ContactMethod, collection: true
27
+
28
+ # Physical-venue fields (populated when kind == "physical").
29
+ attribute :unlocode, :string
30
+ attribute :iata_code, :string
31
+ attribute :address, :string
32
+ attribute :city, :string
33
+ attribute :country_code, :string
34
+ attribute :lat, :float
35
+ attribute :lon, :float
36
+ attribute :building, :string
37
+ attribute :floor, :string
38
+ attribute :room, :string
39
+ attribute :access_notes, :string
40
+
41
+ # Virtual-venue fields (populated when kind == "virtual").
42
+ attribute :uri, :string
43
+ attribute :features, :string, collection: true, values: Enums::VIRTUAL_FEATURE
44
+ attribute :passcode, :string
45
+ attribute :meeting_id, :string
46
+ attribute :dial_in_numbers, :string, collection: true
47
+ attribute :waiting_room, :boolean
48
+ attribute :registration_required, :boolean
49
+
50
+ attribute :extensions, MeetingExtension, collection: true
51
+
52
+ def physical?
53
+ kind == "physical"
54
+ end
55
+
56
+ def virtual?
57
+ kind == "virtual"
58
+ end
59
+
60
+ # Resolve the UN/LOCODE entry via the canonical `unlocodes` gem.
61
+ # Returns an Unlocodes::Entry or nil when the code is empty / unknown.
62
+ def unlocode_entry
63
+ return nil if unlocode.nil? || unlocode.to_s.empty?
64
+
65
+ Edoxen::ReferenceData.find_unlocode(unlocode)
66
+ end
67
+
68
+ def iata_entry
69
+ return nil if iata_code.nil? || iata_code.to_s.empty?
70
+
71
+ Edoxen::ReferenceData.find_iata(iata_code)
72
+ end
73
+
74
+ def features_list
75
+ return "" if features.nil? || features.empty?
76
+
77
+ features.join(", ")
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # VenueValidator — validates Venue instances using the `unlocodes`
5
+ # and `iata` gems. Returns a list of errors; empty list means valid.
6
+ #
7
+ # For PhysicalVenue with a `unlocode` or `iata_code` field, validates
8
+ # that the code exists in the canonical registries. Optionally
9
+ # auto-populates city/country_code/coordinates from the registry.
10
+ class VenueValidator
11
+ attr_reader :venue, :errors
12
+
13
+ def initialize(venue)
14
+ @venue = venue
15
+ @errors = []
16
+ end
17
+
18
+ def validate(auto_populate: false)
19
+ validate_physical(auto_populate: auto_populate) if venue.physical?
20
+
21
+ errors
22
+ end
23
+
24
+ def valid?
25
+ validate
26
+ errors.empty?
27
+ end
28
+
29
+ private
30
+
31
+ def validate_physical(auto_populate:)
32
+ validate_unlocode(auto_populate: auto_populate) if venue.unlocode && !venue.unlocode.to_s.empty?
33
+ validate_iata_code if venue.iata_code && !venue.iata_code.to_s.empty?
34
+ end
35
+
36
+ def validate_unlocode(auto_populate:)
37
+ entry = Edoxen::ReferenceData.find_unlocode(venue.unlocode)
38
+ if entry.nil?
39
+ errors << "Unknown UN/LOCODE: #{venue.unlocode}"
40
+ return
41
+ end
42
+
43
+ return unless auto_populate
44
+
45
+ venue.city ||= entry.name
46
+ venue.country_code ||= entry.country
47
+ end
48
+
49
+ def validate_iata_code
50
+ entry = Edoxen::ReferenceData.find_iata(venue.iata_code)
51
+ return if entry
52
+
53
+ errors << "Unknown IATA code: #{venue.iata_code}"
54
+ end
55
+ end
56
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Edoxen
4
- VERSION = "0.7.2"
4
+ VERSION = "2.1.1"
5
5
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # VirtualVenue — type marker for a Venue with kind == "virtual".
5
+ #
6
+ # The wire format is flat on Venue (all fields live there). This
7
+ # subclass exists for type-checking and to host virtual-only helpers
8
+ # so callers can ask `venue.is_a?(VirtualVenue)` and validators
9
+ # can dispatch by type.
10
+ class VirtualVenue < Venue
11
+ def initialize(attributes = {})
12
+ super(attributes.merge(kind: "virtual"))
13
+ end
14
+ end
15
+ end
@@ -1,25 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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).
4
+ # A single vote on a Decision, recorded against the Voting instance
5
+ # that captured it. `voting_ref` links to the Voting URN; `decision_ref`
6
+ # links to the Decision URN (often derivable from the Voting's motion,
7
+ # but explicit for clarity).
8
+ #
9
+ # `role` is open for adopter-defined values: teller (parliamentary
10
+ # division counter), proxy_holder, observer, etc.
10
11
  class VoteRecord < Lutaml::Model::Serializable
11
- attribute :resolution_ref, :string
12
+ attribute :decision_ref, :string
13
+ attribute :voting_ref, :string
12
14
  attribute :person, Person
13
15
  attribute :affiliation, :string
14
16
  attribute :vote, :string, values: Enums::VOTE_TYPE
17
+ attribute :role, :string
15
18
  attribute :notes, :string
19
+ attribute :extensions, MeetingExtension, collection: true
16
20
 
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
21
+ def affirmative?
22
+ vote == "affirmative"
23
+ end
24
+
25
+ def teller?
26
+ role == "teller"
23
27
  end
24
28
  end
25
29
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Voting — state machine for a single vote on a Motion.
5
+ # Multiple votings can occur on the same Motion (e.g., unclear voice
6
+ # vote → chair calls a formal division → second Voting instance).
7
+ #
8
+ # State machine (VotingStatus):
9
+ # called → in_progress → decided | withdrawn | deferred
10
+ class Voting < Lutaml::Model::Serializable
11
+ attribute :identifier, :string
12
+ attribute :urn, :string
13
+ attribute :on_motion, :string
14
+ attribute :status, :string, values: Enums::VOTING_STATUS
15
+ attribute :voting_method, :string, values: Enums::VOTING_METHOD
16
+ attribute :called_by, Person
17
+ attribute :called_at, :date_time
18
+ attribute :result_declared_at, :date_time
19
+ attribute :result, :string, values: Enums::VOTING_OUTCOME
20
+ attribute :counts, VotingCounts
21
+ attribute :casting_vote, VoteRecord
22
+ attribute :vote_records, VoteRecord, collection: true
23
+ attribute :extensions, MeetingExtension, collection: true
24
+
25
+ def decided?
26
+ status == "decided"
27
+ end
28
+
29
+ def in_progress?
30
+ status == "in_progress"
31
+ end
32
+
33
+ def passed?
34
+ decided? && result == "passed"
35
+ end
36
+
37
+ def negatived?
38
+ decided? && result == "negatived"
39
+ end
40
+
41
+ def tied?
42
+ decided? && result == "tied"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # VotingCounts — tally for a Voting instance.
5
+ class VotingCounts < Lutaml::Model::Serializable
6
+ attribute :ayes, :integer
7
+ attribute :noes, :integer
8
+ attribute :abstentions, :integer
9
+ attribute :absent, :integer
10
+
11
+ def total
12
+ (ayes || 0) + (noes || 0) + (abstentions || 0) + (absent || 0)
13
+ end
14
+
15
+ def margin
16
+ (ayes || 0) - (noes || 0)
17
+ end
18
+
19
+ def tied?
20
+ ayes == noes
21
+ end
22
+ end
23
+ end
data/lib/edoxen.rb CHANGED
@@ -18,36 +18,41 @@ module Edoxen
18
18
  # There are intentionally NO `require_relative` calls in this gem —
19
19
  # autoload keeps load-order semantics clean and lets us tolerate the
20
20
  # extensive cross-references between model classes
21
- # (Resolution <-> Localization, ResolutionMetadata <-> Localization, etc.).
21
+ # (Decision <-> Localization, DecisionMetadata <-> Localization, etc.).
22
22
  autoload :VERSION, "edoxen/version"
23
23
  autoload :Error, "edoxen/error"
24
24
  autoload :ValidationError, "edoxen/error"
25
25
  autoload :Enums, "edoxen/enums"
26
26
  autoload :ReferenceData, "edoxen/reference_data"
27
27
 
28
- # Information-model classes (one per file, one concept per class).
29
- # Names mirror ../edoxen-model/models/*.lutaml.
28
+ # --- Shared behaviours (mixed into entity + metadata classes) --------
29
+ autoload :BodyVocabularyHost, "edoxen/body_vocabulary_host"
30
+ autoload :LocalizationHost, "edoxen/localization_host"
31
+ autoload :OfficersHost, "edoxen/officers_host"
32
+
33
+ # --- Decision side ----------------------------------------------------
30
34
  autoload :StructuredIdentifier, "edoxen/structured_identifier"
31
35
  autoload :MeetingIdentifier, "edoxen/meeting_identifier"
32
- autoload :ResolutionDate, "edoxen/resolution_date"
36
+ autoload :DecisionDate, "edoxen/decision_date"
33
37
  autoload :Action, "edoxen/action"
34
38
  autoload :Approval, "edoxen/approval"
35
39
  autoload :Consideration, "edoxen/consideration"
36
40
  autoload :SourceUrl, "edoxen/source_url"
37
41
  autoload :Localization, "edoxen/localization"
38
42
  autoload :Url, "edoxen/url"
39
- autoload :ResolutionRelation, "edoxen/resolution_relation"
40
- autoload :Resolution, "edoxen/resolution"
41
- autoload :ResolutionMetadata, "edoxen/resolution_metadata"
42
- autoload :ResolutionCollection, "edoxen/resolution_collection"
43
+ autoload :DecisionRelation, "edoxen/decision_relation"
44
+ autoload :Decision, "edoxen/decision"
45
+ autoload :DecisionMetadata, "edoxen/decision_metadata"
46
+ autoload :DecisionCollection, "edoxen/decision_collection"
43
47
 
44
- # Meeting / Agenda side. Mirrors edoxen-model/models/meeting*.lutaml.
48
+ # --- Meeting/Agenda side ----------------------------------------------
45
49
  autoload :DateRange, "edoxen/date_range"
46
- autoload :Location, "edoxen/location"
50
+ autoload :ContactMethod, "edoxen/contact_method"
51
+ autoload :ContactIdentifier, "edoxen/contact_identifier"
52
+ autoload :Name, "edoxen/name"
53
+ autoload :Contact, "edoxen/contact"
47
54
  autoload :Person, "edoxen/person"
48
55
  autoload :HostRef, "edoxen/host_ref"
49
- autoload :ScheduleItemLocalization, "edoxen/schedule_item_localization"
50
- autoload :ScheduleItem, "edoxen/schedule_item"
51
56
  autoload :Deadline, "edoxen/deadline"
52
57
  autoload :Reference, "edoxen/reference"
53
58
  autoload :AgendaItem, "edoxen/agenda_item"
@@ -62,7 +67,29 @@ module Edoxen
62
67
  autoload :MeetingCollectionMetadata, "edoxen/meeting_collection_metadata"
63
68
  autoload :MeetingCollection, "edoxen/meeting_collection"
64
69
 
65
- # Services.
70
+ # --- v2 broadened-scope entities --------------------------------------
71
+ autoload :ExtensionAttribute, "edoxen/extension_attribute"
72
+ autoload :MeetingExtension, "edoxen/meeting_extension"
73
+ autoload :EntityRef, "edoxen/entity_ref"
74
+ autoload :BodyVocabularyEntry, "edoxen/body_vocabulary_entry"
75
+ autoload :Venue, "edoxen/venue"
76
+ autoload :PhysicalVenue, "edoxen/physical_venue"
77
+ autoload :VirtualVenue, "edoxen/virtual_venue"
78
+ autoload :Motion, "edoxen/motion"
79
+ autoload :VotingCounts, "edoxen/voting_counts"
80
+ autoload :Voting, "edoxen/voting"
81
+ autoload :TopicDocument, "edoxen/topic_document"
82
+ autoload :TopicAsset, "edoxen/topic_asset"
83
+ autoload :Topic, "edoxen/topic"
84
+ autoload :RecurrenceByDay, "edoxen/recurrence_by_day"
85
+ autoload :Recurrence, "edoxen/recurrence"
86
+ autoload :MeetingSeries, "edoxen/meeting_series"
87
+ autoload :ComponentLocalization, "edoxen/component_localization"
88
+ autoload :MeetingComponent, "edoxen/meeting_component"
89
+ autoload :Officer, "edoxen/officer"
90
+ autoload :VenueValidator, "edoxen/venue_validator"
91
+
92
+ # --- Services ---------------------------------------------------------
66
93
  autoload :SchemaValidator, "edoxen/schema_validator"
67
94
  autoload :LinkChecker, "edoxen/link_checker"
68
95
  autoload :Cli, "edoxen/cli"