edoxen 0.7.2 → 0.8.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.
- 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 +18 -5
- data/lib/edoxen/approval.rb +2 -2
- data/lib/edoxen/attendance.rb +16 -10
- data/lib/edoxen/body.rb +26 -0
- data/lib/edoxen/body_register.rb +17 -0
- 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 +37 -0
- data/lib/edoxen/contact_identifier.rb +13 -0
- data/lib/edoxen/contact_method.rb +16 -0
- data/lib/edoxen/contact_register.rb +26 -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/entity_resolver.rb +68 -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 +36 -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/urn_for.rb +68 -0
- data/lib/edoxen/venue.rb +81 -0
- data/lib/edoxen/venue_register.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 +51 -15
- data/schema/edoxen.yaml +1427 -246
- data/schema/meeting.yaml +1311 -308
- data/sig/edoxen.rbs +581 -1
- metadata +66 -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
data/lib/edoxen/consideration.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Edoxen
|
|
4
|
-
# Basis for a
|
|
5
|
-
# one effective date and the
|
|
4
|
+
# Basis for a Decision: a verb (having, noting, considering, ...) plus
|
|
5
|
+
# one effective date and the per-field Localized reasoning. Each
|
|
6
|
+
# `Consideration` belongs to a `Decision`; the message field carries
|
|
7
|
+
# one `LocalizedString` per ISO 24229 spelling.
|
|
6
8
|
class Consideration < Lutaml::Model::Serializable
|
|
7
9
|
attribute :type, :string, values: Enums::CONSIDERATION_TYPE
|
|
8
|
-
attribute :date_effective,
|
|
9
|
-
attribute :message, :
|
|
10
|
+
attribute :date_effective, DecisionDate
|
|
11
|
+
attribute :message, LocalizedString, collection: true
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# VCARD-like abstract contact. Generalises Person for cases where the
|
|
5
|
+
# contact may be a person, an organisation, a department, a role
|
|
6
|
+
# ("Secretariat"), or any other entity with a name and one or more
|
|
7
|
+
# communication channels.
|
|
8
|
+
#
|
|
9
|
+
# 1.0 (per-field localization, ISO 24229):
|
|
10
|
+
# - All translatable fields are Localized<String/Name>[0..*]
|
|
11
|
+
# (one entry per spelling/conversion system code).
|
|
12
|
+
# - Added: `urn` for registry storage; `ref` for reference-by-URN.
|
|
13
|
+
#
|
|
14
|
+
# Reference vs inline: when `ref` is set, treat this Contact as a
|
|
15
|
+
# reference (other fields ignored). When `ref` is unset, the Contact
|
|
16
|
+
# is inline (full data). Both patterns are valid on every field that
|
|
17
|
+
# accepts a Contact.
|
|
18
|
+
class Contact < Lutaml::Model::Serializable
|
|
19
|
+
attribute :ref, :string
|
|
20
|
+
attribute :local_ref, :string
|
|
21
|
+
attribute :urn, :string
|
|
22
|
+
attribute :name, LocalizedName, collection: true
|
|
23
|
+
attribute :kind, :string
|
|
24
|
+
attribute :role, :string
|
|
25
|
+
attribute :title, LocalizedString, collection: true
|
|
26
|
+
attribute :affiliation, LocalizedString, collection: true
|
|
27
|
+
attribute :contact_methods, ContactMethod, collection: true
|
|
28
|
+
attribute :identifiers, ContactIdentifier, collection: true
|
|
29
|
+
attribute :address, LocalizedString, collection: true
|
|
30
|
+
attribute :extensions, MeetingExtension, collection: true
|
|
31
|
+
|
|
32
|
+
def reference?
|
|
33
|
+
(!ref.nil? && !ref.to_s.empty?) ||
|
|
34
|
+
(!local_ref.nil? && !local_ref.to_s.empty?)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
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,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 ContactRegister.
|
|
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 ContactRegister < 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,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
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Edoxen
|
|
4
|
+
# EntityResolver — resolves Contact/Venue/Body references by walking
|
|
5
|
+
# the three-tier scope hierarchy:
|
|
6
|
+
#
|
|
7
|
+
# 1. Inline: if the entity has no +ref+ and no +local_ref+, return
|
|
8
|
+
# it as-is (it carries full data).
|
|
9
|
+
# 2. Document-scoped: if +local_ref+ is set, look up in the
|
|
10
|
+
# document-scoped collection (e.g. Meeting#contacts) by matching
|
|
11
|
+
# +urn+ against +local_ref+.
|
|
12
|
+
# 3. Global register: if +ref+ is set, look up in the global
|
|
13
|
+
# register (e.g. ContactRegister) by matching +urn+ against +ref+.
|
|
14
|
+
#
|
|
15
|
+
# Pure service: no mutation. Returns the resolved entity or nil.
|
|
16
|
+
class EntityResolver
|
|
17
|
+
attr_reader :scoped_collections, :global_registers
|
|
18
|
+
|
|
19
|
+
def initialize(scoped: {}, registers: {})
|
|
20
|
+
@scoped_collections = scoped
|
|
21
|
+
@global_registers = registers
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resolve(entity)
|
|
25
|
+
return entity unless entity.reference?
|
|
26
|
+
|
|
27
|
+
resolve_local(entity) || resolve_global(entity)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def resolve_all(entities)
|
|
31
|
+
entities.map { |e| resolve(e) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def resolve_local(entity)
|
|
37
|
+
return nil if entity.local_ref.nil? || entity.local_ref.to_s.empty?
|
|
38
|
+
|
|
39
|
+
collection = find_scoped_collection(entity.class)
|
|
40
|
+
return nil unless collection
|
|
41
|
+
|
|
42
|
+
collection.find { |member| member_key(member) == entity.local_ref }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def resolve_global(entity)
|
|
46
|
+
return nil if entity.ref.nil? || entity.ref.to_s.empty?
|
|
47
|
+
|
|
48
|
+
register = find_global_register(entity.class)
|
|
49
|
+
return nil unless register
|
|
50
|
+
|
|
51
|
+
register.find_by_urn(entity.ref)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def find_scoped_collection(klass)
|
|
55
|
+
@scoped_collections[klass] ||
|
|
56
|
+
@scoped_collections[klass.superclass]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def find_global_register(klass)
|
|
60
|
+
@global_registers[klass] ||
|
|
61
|
+
@global_registers[klass.superclass]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def member_key(member)
|
|
65
|
+
member.urn
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
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)
|