edoxen 0.7.2 → 0.8.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +26 -1
- data/.rubocop_todo.yml +1 -1
- data/CHANGELOG.md +133 -0
- data/README.adoc +414 -182
- data/edoxen.gemspec +10 -10
- data/lib/edoxen/action.rb +5 -4
- data/lib/edoxen/agenda.rb +4 -7
- data/lib/edoxen/agenda_item.rb +12 -5
- data/lib/edoxen/approval.rb +2 -2
- data/lib/edoxen/attendance.rb +16 -10
- data/lib/edoxen/body_vocabulary_entry.rb +24 -0
- data/lib/edoxen/body_vocabulary_host.rb +40 -0
- data/lib/edoxen/cli.rb +175 -115
- data/lib/edoxen/consideration.rb +6 -4
- data/lib/edoxen/contact.rb +35 -0
- data/lib/edoxen/contact_collection.rb +26 -0
- data/lib/edoxen/contact_identifier.rb +13 -0
- data/lib/edoxen/contact_method.rb +16 -0
- data/lib/edoxen/date_time_range.rb +16 -0
- data/lib/edoxen/deadline.rb +1 -1
- data/lib/edoxen/decision.rb +57 -0
- data/lib/edoxen/decision_collection.rb +10 -0
- data/lib/edoxen/decision_date.rb +9 -0
- data/lib/edoxen/decision_metadata.rb +27 -0
- data/lib/edoxen/decision_relation.rb +11 -0
- data/lib/edoxen/declaration.rb +25 -0
- data/lib/edoxen/entity_ref.rb +74 -0
- data/lib/edoxen/enums.rb +123 -9
- data/lib/edoxen/error.rb +1 -1
- data/lib/edoxen/extension_attribute.rb +78 -0
- data/lib/edoxen/host_ref.rb +1 -1
- data/lib/edoxen/link_checker.rb +143 -34
- data/lib/edoxen/localized_name.rb +16 -0
- data/lib/edoxen/localized_string.rb +22 -0
- data/lib/edoxen/meeting.rb +34 -42
- data/lib/edoxen/meeting_collection.rb +1 -2
- data/lib/edoxen/meeting_collection_metadata.rb +4 -2
- data/lib/edoxen/meeting_component.rb +37 -0
- data/lib/edoxen/meeting_extension.rb +25 -0
- data/lib/edoxen/meeting_identifier.rb +1 -1
- data/lib/edoxen/meeting_series.rb +26 -0
- data/lib/edoxen/minutes.rb +3 -4
- data/lib/edoxen/minutes_section.rb +12 -13
- data/lib/edoxen/motion.rb +51 -0
- data/lib/edoxen/name.rb +29 -0
- data/lib/edoxen/officer.rb +19 -0
- data/lib/edoxen/officers_host.rb +19 -0
- data/lib/edoxen/person.rb +5 -9
- data/lib/edoxen/physical_venue.rb +15 -0
- data/lib/edoxen/recurrence.rb +27 -0
- data/lib/edoxen/recurrence_by_day.rb +10 -0
- data/lib/edoxen/reference.rb +1 -1
- data/lib/edoxen/reference_data.rb +34 -44
- data/lib/edoxen/schema_validator.rb +3 -4
- data/lib/edoxen/source_url.rb +4 -4
- data/lib/edoxen/statement.rb +21 -0
- data/lib/edoxen/structured_identifier.rb +2 -2
- data/lib/edoxen/topic.rb +39 -0
- data/lib/edoxen/topic_asset.rb +14 -0
- data/lib/edoxen/topic_document.rb +16 -0
- data/lib/edoxen/venue.rb +79 -0
- data/lib/edoxen/venue_collection.rb +18 -0
- data/lib/edoxen/venue_validator.rb +74 -0
- data/lib/edoxen/version.rb +1 -1
- data/lib/edoxen/virtual_venue.rb +15 -0
- data/lib/edoxen/vote_record.rb +17 -13
- data/lib/edoxen/voting.rb +45 -0
- data/lib/edoxen/voting_counts.rb +23 -0
- data/lib/edoxen.rb +47 -15
- data/schema/edoxen.yaml +1376 -246
- data/schema/meeting.yaml +1207 -272
- data/sig/edoxen.rbs +581 -1
- metadata +62 -22
- data/lib/edoxen/_metadata.rb.deprecated +0 -57
- data/lib/edoxen/localization.rb +0 -18
- data/lib/edoxen/location.rb +0 -15
- data/lib/edoxen/meeting_localization.rb +0 -15
- data/lib/edoxen/resolution.rb +0 -43
- data/lib/edoxen/resolution_collection.rb +0 -10
- data/lib/edoxen/resolution_date.rb +0 -11
- data/lib/edoxen/resolution_metadata.rb +0 -30
- data/lib/edoxen/resolution_relation.rb +0 -11
- data/lib/edoxen/resolution_set.rb +0 -17
- data/lib/edoxen/schedule_item.rb +0 -27
- data/lib/edoxen/schedule_item_localization.rb +0 -21
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# Registry of Contacts indexed by scoped URN. Members carry
|
|
5
|
+
# `urn: urn:edoxen:contact:{scope}:{local-id}`; the collection's
|
|
6
|
+
# `scope` MUST match the scope segment in member URNs.
|
|
7
|
+
#
|
|
8
|
+
# Other documents (Meeting, MeetingComponent, HostRef, etc.) reference
|
|
9
|
+
# contacts via `ref: urn:edoxen:contact:{scope}:{local-id}` and
|
|
10
|
+
# resolve against the matching ContactCollection.
|
|
11
|
+
#
|
|
12
|
+
# Storage: single YAML file (typical) or YAML Stream (one Contact per
|
|
13
|
+
# document, for large registries that need partial updates). Stream
|
|
14
|
+
# loading is a service-layer concern (file-system I/O); the model
|
|
15
|
+
# owns only the (de)serialisation of one collection.
|
|
16
|
+
class ContactCollection < Lutaml::Model::Serializable
|
|
17
|
+
attribute :scope, :string
|
|
18
|
+
attribute :title, LocalizedString, collection: true
|
|
19
|
+
attribute :contacts, Contact, collection: true
|
|
20
|
+
attribute :extensions, MeetingExtension, collection: true
|
|
21
|
+
|
|
22
|
+
def find_by_urn(urn)
|
|
23
|
+
contacts&.find { |c| c.urn == urn }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# One polymorphic external identifier for a Contact — ORCID, ISNI,
|
|
5
|
+
# Wikidata QID, ROR, Ringgold, GitHub handle, etc. Replaces the hard-
|
|
6
|
+
# coded `orcid` field. OCP: new identifier schemes are added via the
|
|
7
|
+
# ContactIdentifierKind enum (or `other` + extensions).
|
|
8
|
+
class ContactIdentifier < Lutaml::Model::Serializable
|
|
9
|
+
attribute :kind, :string, values: Enums::CONTACT_IDENTIFIER_KIND
|
|
10
|
+
attribute :value, :string
|
|
11
|
+
attribute :extensions, MeetingExtension, collection: true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# One polymorphic communication channel — phone, email, fax, url, mail,
|
|
5
|
+
# etc. Replaces the hard-coded `email` / `phone` fields that used to
|
|
6
|
+
# live on Person and Venue. New channel kinds are added via the
|
|
7
|
+
# ContactMethodKind enum (or `other` + extensions for adopter-specific
|
|
8
|
+
# channels); the model itself never needs to change (OCP).
|
|
9
|
+
class ContactMethod < Lutaml::Model::Serializable
|
|
10
|
+
attribute :kind, :string, values: Enums::CONTACT_METHOD_KIND
|
|
11
|
+
attribute :value, :string
|
|
12
|
+
attribute :label, :string
|
|
13
|
+
attribute :primary, :boolean
|
|
14
|
+
attribute :extensions, MeetingExtension, collection: true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# Start + end pair with sub-day precision. Parallel to DateRange;
|
|
5
|
+
# use when day granularity is insufficient — e.g.
|
|
6
|
+
# `Meeting#occurred_date_range` (a meeting that ran 09:00–11:30).
|
|
7
|
+
#
|
|
8
|
+
# The two are intentionally separate types so the granularity is
|
|
9
|
+
# visible at the type level: a caller who sees DateRange knows
|
|
10
|
+
# it is day-granularity; a caller who sees DateTimeRange knows
|
|
11
|
+
# sub-day precision is available.
|
|
12
|
+
class DateTimeRange < Lutaml::Model::Serializable
|
|
13
|
+
attribute :start, :date_time
|
|
14
|
+
attribute :end, :date_time
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/edoxen/deadline.rb
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# A formal Decision — the base type for any outcome adopted by a Meeting.
|
|
5
|
+
# Was `Resolution` in legacy; renamed in 1.0 because the same formal concept
|
|
6
|
+
# has many names across bodies (resolution, order, ruling, determination,
|
|
7
|
+
# finding, opinion). `Resolution` is one `kind` value, not a class name.
|
|
8
|
+
#
|
|
9
|
+
# 1.0 (per-field localization, ISO 24229):
|
|
10
|
+
# - Removed `localizations[]` collection.
|
|
11
|
+
# - Per-field LocalizedString for title, subject, message, considering.
|
|
12
|
+
class Decision < Lutaml::Model::Serializable
|
|
13
|
+
attribute :identifier, StructuredIdentifier, collection: true
|
|
14
|
+
attribute :kind, :string, values: Enums::DECISION_KIND
|
|
15
|
+
attribute :status, :string, values: Enums::DECISION_STATUS
|
|
16
|
+
attribute :doi, :string
|
|
17
|
+
attribute :urn, :string
|
|
18
|
+
attribute :agenda_item, :string
|
|
19
|
+
attribute :dates, DecisionDate, collection: true
|
|
20
|
+
attribute :categories, :string, collection: true
|
|
21
|
+
attribute :meeting, MeetingIdentifier
|
|
22
|
+
attribute :relations, DecisionRelation, collection: true
|
|
23
|
+
attribute :urls, Url, collection: true
|
|
24
|
+
attribute :brought_by_motions, :string, collection: true
|
|
25
|
+
attribute :about_topics, :string, collection: true
|
|
26
|
+
attribute :made_in_component, :string
|
|
27
|
+
attribute :body_type, :string
|
|
28
|
+
attribute :title, LocalizedString, collection: true
|
|
29
|
+
attribute :subject, LocalizedString, collection: true
|
|
30
|
+
attribute :message, LocalizedString, collection: true
|
|
31
|
+
attribute :considering, LocalizedString, collection: true
|
|
32
|
+
attribute :considerations, Consideration, collection: true
|
|
33
|
+
attribute :approvals, Approval, collection: true
|
|
34
|
+
attribute :actions, Action, collection: true
|
|
35
|
+
attribute :extensions, MeetingExtension, collection: true
|
|
36
|
+
|
|
37
|
+
# --- 1.0 derivation accessors (1.0 design review) -------------------
|
|
38
|
+
|
|
39
|
+
def brought_by_motions_in(meeting:)
|
|
40
|
+
return [] unless meeting && urn
|
|
41
|
+
|
|
42
|
+
meeting.motions.select do |motion|
|
|
43
|
+
motion.resulting_decision == urn ||
|
|
44
|
+
motion.resulting_decision_ref&.urn == urn
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def component_in(meeting:)
|
|
49
|
+
return nil unless meeting && made_in_component
|
|
50
|
+
|
|
51
|
+
meeting.components&.find do |component|
|
|
52
|
+
component.identifier == made_in_component ||
|
|
53
|
+
component.urn == made_in_component
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# Top-level container for a published decision collection: metadata
|
|
5
|
+
# plus the list of decisions. Was ResolutionCollection in legacy.
|
|
6
|
+
class DecisionCollection < Lutaml::Model::Serializable
|
|
7
|
+
attribute :metadata, DecisionMetadata
|
|
8
|
+
attribute :decisions, Decision, collection: true
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# Collection-level metadata: the localized title, the meeting date,
|
|
5
|
+
# the source secretariat, per-language source PDFs, and the host venue.
|
|
6
|
+
class DecisionMetadata < Lutaml::Model::Serializable
|
|
7
|
+
include BodyVocabularyHost
|
|
8
|
+
|
|
9
|
+
attribute :title, LocalizedString, collection: true
|
|
10
|
+
attribute :date, :date
|
|
11
|
+
attribute :source, :string
|
|
12
|
+
attribute :source_urls, SourceUrl, collection: true
|
|
13
|
+
attribute :city, :string
|
|
14
|
+
attribute :country_code, :string
|
|
15
|
+
|
|
16
|
+
# URN back-reference to the Meeting that produced this collection.
|
|
17
|
+
attribute :meeting_urn, :string
|
|
18
|
+
|
|
19
|
+
attribute :extensions, MeetingExtension, collection: true
|
|
20
|
+
|
|
21
|
+
def city_entry
|
|
22
|
+
return nil if city.nil? || city.to_s.empty?
|
|
23
|
+
|
|
24
|
+
Edoxen::ReferenceData.find_unlocode(city)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# Directed relation between two decisions, identified by their
|
|
5
|
+
# StructuredIdentifier (prefix + number).
|
|
6
|
+
class DecisionRelation < Lutaml::Model::Serializable
|
|
7
|
+
attribute :source, StructuredIdentifier
|
|
8
|
+
attribute :destination, StructuredIdentifier
|
|
9
|
+
attribute :type, :string, values: Enums::DECISION_RELATION_TYPE
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# A formal declaration made by one or more meeting members.
|
|
5
|
+
# Covers the two BS 0:2006 declaration types via the `kind`
|
|
6
|
+
# discriminator: `conflict_of_interest` and `ipr`.
|
|
7
|
+
#
|
|
8
|
+
# The IPR-specific fields (`ipr_subject_ref`, `ipr_target_ref`)
|
|
9
|
+
# are populated only when `kind == "ipr"`. This mirrors edoxen's
|
|
10
|
+
# "flat class, kind discriminates which subset is populated"
|
|
11
|
+
# pattern (cf. Venue: physical + virtual fields on one flat
|
|
12
|
+
# class).
|
|
13
|
+
#
|
|
14
|
+
# Attachment points: `Meeting#declarations[]` (per-meeting) and
|
|
15
|
+
# `Topic#declarations[]` (standing position that travels with
|
|
16
|
+
# the topic).
|
|
17
|
+
class Declaration < Lutaml::Model::Serializable
|
|
18
|
+
attribute :kind, :string, values: Enums::DECLARATION_KIND
|
|
19
|
+
attribute :description, LocalizedString, collection: true
|
|
20
|
+
attribute :party, Person, collection: true
|
|
21
|
+
attribute :ipr_subject_ref, EntityRef
|
|
22
|
+
attribute :ipr_target_ref, EntityRef
|
|
23
|
+
attribute :extensions, MeetingExtension, collection: true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# EntityRef — a typed cross-reference between entities (1.0, 1.0 design review).
|
|
5
|
+
#
|
|
6
|
+
# Single identity: exactly one of `urn`, `identifier`, or `local_ref`
|
|
7
|
+
# must be set. Optional metadata: `kind`, `role`, `note`.
|
|
8
|
+
#
|
|
9
|
+
# Encapsulation:
|
|
10
|
+
# - Read the identity via `#resolved_identity` (returns the canonical
|
|
11
|
+
# form — URN string, StructuredIdentifier, or local key).
|
|
12
|
+
# - Validate via `#valid?` (true when exactly one identity field is
|
|
13
|
+
# set; `#multiple_identities?` flags the rare ambiguous case).
|
|
14
|
+
#
|
|
15
|
+
# Pilot: `Motion.resulting_decision_ref` (parallel to the existing
|
|
16
|
+
# `resulting_decision: String`). 1.0 will remove the String form.
|
|
17
|
+
class EntityRef < Lutaml::Model::Serializable
|
|
18
|
+
attribute :urn, :string
|
|
19
|
+
attribute :identifier, StructuredIdentifier
|
|
20
|
+
attribute :local_ref, :string
|
|
21
|
+
|
|
22
|
+
attribute :kind, :string
|
|
23
|
+
attribute :role, :string
|
|
24
|
+
attribute :note, :string
|
|
25
|
+
|
|
26
|
+
# True when exactly one identity field is set. The wire contract
|
|
27
|
+
# (1.0 design review + JSON-Schema) is XOR: setting 0 or ≥2 identity
|
|
28
|
+
# fields is a data error.
|
|
29
|
+
def valid?
|
|
30
|
+
identities_set.size == 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# True when two or more identity fields are set. Useful for
|
|
34
|
+
# migration tooling that wants to warn on ambiguity without
|
|
35
|
+
# rejecting outright.
|
|
36
|
+
def multiple_identities?
|
|
37
|
+
identities_set.size > 1
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the canonical identity (URN > identifier > local_ref
|
|
41
|
+
# precedence). Callers that need a single value should validate
|
|
42
|
+
# first via `#valid?`; on a multiple-identity ref the precedence
|
|
43
|
+
# is still applied but the data is suspect.
|
|
44
|
+
def resolved_identity
|
|
45
|
+
return urn if urn && !urn.to_s.empty?
|
|
46
|
+
return identifier if identifier
|
|
47
|
+
return local_ref if local_ref && !local_ref.to_s.empty?
|
|
48
|
+
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_s
|
|
53
|
+
identity = resolved_identity
|
|
54
|
+
return "(invalid EntityRef)" unless identity
|
|
55
|
+
|
|
56
|
+
case identity
|
|
57
|
+
when StructuredIdentifier then "#{identity.prefix}/#{identity.number}"
|
|
58
|
+
else identity.to_s
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def identities_set
|
|
65
|
+
[urn, identifier, local_ref].reject do |value|
|
|
66
|
+
case value
|
|
67
|
+
when nil then true
|
|
68
|
+
when String then value.empty?
|
|
69
|
+
else false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/edoxen/enums.rb
CHANGED
|
@@ -26,13 +26,33 @@ module Edoxen
|
|
|
26
26
|
emphasizing concerned accepts observing referring acting empowers
|
|
27
27
|
].freeze
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# --- Decision-side (was Resolution; renamed 2026-07) -----------------
|
|
30
|
+
|
|
31
|
+
# DecisionKind — what kind of formal outcome this is. `resolution` is
|
|
32
|
+
# one value; the same formal concept has many names across bodies
|
|
33
|
+
# (order, ruling, determination, finding, opinion, etc.).
|
|
34
|
+
DECISION_KIND = %w[
|
|
35
|
+
resolution order ruling determination
|
|
36
|
+
recommendation statement finding opinion other
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
# DecisionKindCanonical — short abstract classification of decision
|
|
40
|
+
# kinds. Cap: 5 canonical values (hard limit per the design review).
|
|
41
|
+
DECISION_KIND_CANONICAL = %w[decision recommendation statement finding other].freeze
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
# DecisionStatus — lifecycle state machine.
|
|
44
|
+
DECISION_STATUS = %w[
|
|
45
|
+
draft proposed under_consideration
|
|
46
|
+
decided negatived withdrawn deferred
|
|
33
47
|
].freeze
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
DECISION_RELATION_TYPE = %w[
|
|
50
|
+
annex_of has_annex updates refines replaces considers cites
|
|
51
|
+
].freeze
|
|
52
|
+
|
|
53
|
+
DECISION_DATE_TYPE = %w[
|
|
54
|
+
adoption drafted discussed proposed decided negatived withdrawn published effective
|
|
55
|
+
].freeze
|
|
36
56
|
|
|
37
57
|
APPROVAL_TYPE = %w[affirmative negative].freeze
|
|
38
58
|
|
|
@@ -44,31 +64,125 @@ module Edoxen
|
|
|
44
64
|
# Mirrors edoxen-model/models/{meeting,agenda,...}_*.lutaml.
|
|
45
65
|
# schema/meeting.yaml references these via $defs/<EnumName>.enum.
|
|
46
66
|
|
|
47
|
-
MEETING_TYPE = %w[
|
|
67
|
+
MEETING_TYPE = %w[
|
|
68
|
+
plenary working_group task_group ad_hoc joint general_assembly
|
|
69
|
+
committee subcommittee conference workshop seminar webinar hearing
|
|
70
|
+
markup board_meeting annual_general_meeting other
|
|
71
|
+
].freeze
|
|
72
|
+
|
|
73
|
+
# MeetingTypeCanonical — short abstract classification of meeting
|
|
74
|
+
# types. Cap: 4 canonical values (no `other`).
|
|
75
|
+
MEETING_TYPE_CANONICAL = %w[plenary governing working advisory].freeze
|
|
48
76
|
|
|
49
77
|
MEETING_STATUS = %w[upcoming completed cancelled].freeze
|
|
50
78
|
|
|
51
79
|
AGENDA_STATUS = %w[draft final amended cancelled superseded].freeze
|
|
52
80
|
|
|
53
|
-
AGENDA_ITEM_KIND = %w[numbered unnumbered header opening closing].freeze
|
|
81
|
+
AGENDA_ITEM_KIND = %w[numbered unnumbered header opening closing aob].freeze
|
|
54
82
|
|
|
55
|
-
AGENDA_ITEM_OUTCOME = %w[discussed resolved deferred adopted withdrawn].freeze
|
|
83
|
+
AGENDA_ITEM_OUTCOME = %w[discussed resolved deferred adopted withdrawn carried negatived].freeze
|
|
56
84
|
|
|
57
85
|
HOST_TYPE = %w[national_body liaison associate organizer].freeze
|
|
58
86
|
|
|
59
87
|
MEETING_RELATION_TYPE = %w[
|
|
60
88
|
continues_from continues_to joint_with supersedes superseded_by rescheduled_from rescheduled_to
|
|
89
|
+
parent_of child_of sibling_of depends_on
|
|
90
|
+
finish_to_start finish_to_finish start_to_start start_to_finish
|
|
61
91
|
].freeze
|
|
62
92
|
|
|
63
93
|
SOURCE_URL_KIND = %w[
|
|
64
|
-
agenda_pdf minutes_pdf
|
|
94
|
+
agenda_pdf minutes_pdf decisions_pdf report_pdf register_url landing_page
|
|
65
95
|
].freeze
|
|
66
96
|
|
|
67
|
-
# P2.6 / P2.7 — Attendance and Vote records.
|
|
68
97
|
PARTICIPATION_STATUS = %w[present absent apologies observer excused].freeze
|
|
69
98
|
|
|
70
99
|
VOTE_TYPE = %w[
|
|
71
100
|
affirmative negative abstain absent not_applicable
|
|
72
101
|
].freeze
|
|
102
|
+
|
|
103
|
+
# --- New in 1.0 (broadened scope) -----------------------------------
|
|
104
|
+
|
|
105
|
+
VENUE_KIND = %w[physical virtual].freeze
|
|
106
|
+
|
|
107
|
+
VIRTUAL_FEATURE = %w[audio video chat phone screen feed].freeze
|
|
108
|
+
|
|
109
|
+
VISIBILITY = %w[public private confidential].freeze
|
|
110
|
+
|
|
111
|
+
ATTENDANCE_ROLE = %w[chair required optional non_participant].freeze
|
|
112
|
+
|
|
113
|
+
# iCalendar PARTSTAT, plain English (no jargon).
|
|
114
|
+
ATTENDANCE_RESPONSE = %w[
|
|
115
|
+
pending confirmed declined tentative delegated
|
|
116
|
+
].freeze
|
|
117
|
+
|
|
118
|
+
COMPONENT_KIND = %w[
|
|
119
|
+
track session debate breakout bof
|
|
120
|
+
plenary_session working_group_session committee_of_the_whole
|
|
121
|
+
keynote address statement question_time
|
|
122
|
+
opening closing break reception registration networking other
|
|
123
|
+
].freeze
|
|
124
|
+
|
|
125
|
+
# ComponentKindCanonical — the short abstract set (1.0, 1.0 design review).
|
|
126
|
+
# `other` is a temporary escape while the vocabulary stabilises.
|
|
127
|
+
#
|
|
128
|
+
# Cap: 5 canonical values.
|
|
129
|
+
COMPONENT_KIND_CANONICAL = %w[deliberative working ceremonial break other].freeze
|
|
130
|
+
|
|
131
|
+
MOTION_STATUS = %w[
|
|
132
|
+
introduced seconded debating question_put voting
|
|
133
|
+
carried negatived withdrawn lapsed
|
|
134
|
+
].freeze
|
|
135
|
+
|
|
136
|
+
# Terminal MotionStatus values — `Motion#pending?` is the complement.
|
|
137
|
+
# Kept as a separate constant (not derived) because the partition is
|
|
138
|
+
# semantic, not lexical; the spec in motion_spec.rb asserts the union
|
|
139
|
+
# equals MOTION_STATUS and the intersection is empty (MECE).
|
|
140
|
+
MOTION_TERMINAL = %w[carried negatived withdrawn lapsed].freeze
|
|
141
|
+
|
|
142
|
+
VOTING_STATUS = %w[called in_progress decided withdrawn deferred].freeze
|
|
143
|
+
|
|
144
|
+
VOTING_METHOD = %w[
|
|
145
|
+
voice division show_of_hands roll_call electronic
|
|
146
|
+
secret_ballot unanimous_consent consensus
|
|
147
|
+
].freeze
|
|
148
|
+
|
|
149
|
+
VOTING_OUTCOME = %w[passed negatived tied withdrawn].freeze
|
|
150
|
+
|
|
151
|
+
TOPIC_STATUS = %w[open under_discussion decided deferred withdrawn].freeze
|
|
152
|
+
|
|
153
|
+
OFFICER_ROLE = %w[
|
|
154
|
+
chair vice_chair deputy_chair secretary treasurer
|
|
155
|
+
parliamentarian presiding_officer sergeant_at_arms other
|
|
156
|
+
].freeze
|
|
157
|
+
|
|
158
|
+
RECURRENCE_FREQ = %w[
|
|
159
|
+
secondly minutely hourly daily weekly monthly yearly
|
|
160
|
+
].freeze
|
|
161
|
+
|
|
162
|
+
# Polymorphic communication channel. OCP: adding a new kind does
|
|
163
|
+
# not change the model — only this enum (or use `other` + extensions).
|
|
164
|
+
CONTACT_METHOD_KIND = %w[
|
|
165
|
+
phone mobile fax email url mail pager message other
|
|
166
|
+
].freeze
|
|
167
|
+
|
|
168
|
+
# Polymorphic external identifier for a Contact (ORCID, ISNI,
|
|
169
|
+
# Wikidata, ROR, etc.). OCP: adding a new scheme only extends this
|
|
170
|
+
# enum (or use `other` + extensions).
|
|
171
|
+
CONTACT_IDENTIFIER_KIND = %w[
|
|
172
|
+
orcid isni wikidata ror ringgold github other
|
|
173
|
+
].freeze
|
|
174
|
+
|
|
175
|
+
# --- BS 0:2006 meeting-minutes concepts --------------------------------
|
|
176
|
+
|
|
177
|
+
# StatementKind — discriminates the three BS 0:2006 statement
|
|
178
|
+
# types. Adding a new kind is a one-line enum extension; the
|
|
179
|
+
# Statement model itself never needs to change (OCP).
|
|
180
|
+
STATEMENT_KIND = %w[statement comment standpoint].freeze
|
|
181
|
+
|
|
182
|
+
# DeclarationKind — discriminates the two BS 0:2006 declaration
|
|
183
|
+
# types (conflict of interest, IPR). Adding a new kind is a
|
|
184
|
+
# one-line enum extension; the Declaration model itself never
|
|
185
|
+
# needs to change (OCP).
|
|
186
|
+
DECLARATION_KIND = %w[conflict_of_interest ipr].freeze
|
|
73
187
|
end
|
|
74
188
|
end
|
data/lib/edoxen/error.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Edoxen
|
|
|
7
7
|
# Unified validation failure shape. Produced by both:
|
|
8
8
|
# * SchemaValidator — JSON-Schema violations and YAML syntax errors
|
|
9
9
|
# carry source: :schema (or :syntax for Psych::SyntaxError).
|
|
10
|
-
# *
|
|
10
|
+
# * DecisionCollection.from_yaml rescues in the CLI — model parse
|
|
11
11
|
# failures carry source: :model.
|
|
12
12
|
#
|
|
13
13
|
# One type at the seam means callers (CLI, future renderers, tests)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# ExtensionAttribute — one typed key/value pair within a
|
|
5
|
+
# MeetingExtension. Polymorphic on value type so consumers don't
|
|
6
|
+
# have to re-parse strings back into Int/Float/Bool/Date.
|
|
7
|
+
#
|
|
8
|
+
# Wire form (1.0+, per 1.0 design review):
|
|
9
|
+
#
|
|
10
|
+
# - key: "quorum"
|
|
11
|
+
# type: "integer"
|
|
12
|
+
# intValue: 7
|
|
13
|
+
#
|
|
14
|
+
# - key: "name"
|
|
15
|
+
# type: "string"
|
|
16
|
+
# value: "quic" # string variant uses bare `value` (1.0 wire)
|
|
17
|
+
#
|
|
18
|
+
# - key: "start"
|
|
19
|
+
# type: "datetime"
|
|
20
|
+
# dateTimeValue: 2026-07-04T10:00:00Z
|
|
21
|
+
#
|
|
22
|
+
# The `type` discriminator tells consumers which value field to read
|
|
23
|
+
# without probing all six. Exactly one of the value fields should be
|
|
24
|
+
# set; the schema enforces this.
|
|
25
|
+
#
|
|
26
|
+
# String values use the bare `value:` wire name for back-compat with
|
|
27
|
+
# 1.0 fixtures. Typed variants (integer/float/boolean/date/datetime)
|
|
28
|
+
# use camelCase wire names. The gem unifies them behind `#typed_value`.
|
|
29
|
+
class ExtensionAttribute < Lutaml::Model::Serializable
|
|
30
|
+
attribute :key, :string
|
|
31
|
+
|
|
32
|
+
# String variant — Ruby attribute is `value`, wire name is `value`
|
|
33
|
+
# (matches 1.0; `type: "string"` discriminator added in 1.0).
|
|
34
|
+
attribute :value, :string
|
|
35
|
+
|
|
36
|
+
# Typed variants — added in 1.0.
|
|
37
|
+
attribute :integer_value, :integer
|
|
38
|
+
attribute :float_value, :float
|
|
39
|
+
attribute :boolean_value, :boolean
|
|
40
|
+
attribute :date_value, :date
|
|
41
|
+
attribute :date_time_value, :date_time
|
|
42
|
+
|
|
43
|
+
# Wire discriminator: "string" | "integer" | "float" | "boolean" |
|
|
44
|
+
# "date" | "datetime". Tells consumers which value field to read.
|
|
45
|
+
attribute :type, :string
|
|
46
|
+
|
|
47
|
+
key_value do
|
|
48
|
+
map "key", to: :key
|
|
49
|
+
map "type", to: :type
|
|
50
|
+
map "value", to: :value
|
|
51
|
+
map "intValue", to: :integer_value
|
|
52
|
+
map "floatValue", to: :float_value
|
|
53
|
+
map "booleanValue", to: :boolean_value
|
|
54
|
+
map "dateValue", to: :date_value
|
|
55
|
+
map "dateTimeValue", to: :date_time_value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Convenience: read whichever typed value is set. Returns nil when
|
|
59
|
+
# none of the value fields is populated.
|
|
60
|
+
def typed_value
|
|
61
|
+
case type
|
|
62
|
+
when "integer" then integer_value
|
|
63
|
+
when "float" then float_value
|
|
64
|
+
when "boolean" then boolean_value unless boolean_value.nil?
|
|
65
|
+
when "date" then date_value
|
|
66
|
+
when "datetime" then date_time_value
|
|
67
|
+
else
|
|
68
|
+
# Default to string for back-compat with 1.0 (no `type`).
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Back-compat alias — 1.0 introduced `value` as the canonical Ruby
|
|
74
|
+
# attribute; the older `string_value` alias keeps callers that
|
|
75
|
+
# already migrated to that name working.
|
|
76
|
+
alias string_value value
|
|
77
|
+
end
|
|
78
|
+
end
|