edoxen 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ # Phase 7 — CLI integration
2
+
3
+ Priority: **P1**
4
+
5
+ ## Goal
6
+
7
+ Add `validate-meetings` and `normalize-meetings` subcommands. Reuse
8
+ the existing `Edoxen::Cli::Batch` deep module — no scaffold
9
+ duplication.
10
+
11
+ ## Deliverables
12
+
13
+ - `lib/edoxen/cli.rb` — two new subcommands:
14
+
15
+ ```ruby
16
+ desc "validate-meetings YAML_FILE_PATTERN",
17
+ "Validate Meeting/Agenda YAML file(s) against schema/meeting.yaml."
18
+
19
+ def validate_meetings(pattern)
20
+ validator = SchemaValidator.new(meeting_schema_path)
21
+ Batch.run(self, pattern, header: "🔍 Validating meetings") do |file|
22
+ errors = validator.validate_file(file) + collect_meeting_model_errors(file)
23
+ errors.empty? ? Batch::Result.ok("VALID") : Batch::Result.bad(errors.map(&:to_clickable_format))
24
+ end
25
+ end
26
+
27
+ desc "normalize-meetings YAML_FILE_PATTERN",
28
+ "Round-trip Meeting YAML file(s) through the model (--output DIR | --inplace)."
29
+
30
+ option :output, type: :string
31
+ option :inplace, type: :boolean
32
+
33
+ def normalize_meetings(pattern)
34
+ # Same shape as normalize — uses Batch.run
35
+ end
36
+ ```
37
+
38
+ - `lib/edoxen/cli.rb` private: `meeting_schema_path`,
39
+ `collect_meeting_model_errors`.
40
+
41
+ ## Why separate subcommands
42
+
43
+ - Different root schema, different model parser.
44
+ - `validate` (existing) is for ResolutionCollection; backward
45
+ compatibility preserved.
46
+ - Future: a `validate-all` that auto-detects via `oneOf` if needed.
47
+
48
+ ## Acceptance criteria
49
+
50
+ - [ ] `edoxen validate-meetings PATTERN` exits 0 on valid fixtures.
51
+ - [ ] `edoxen validate-meetings PATTERN` exits non-zero + lists
52
+ errors on invalid input.
53
+ - [ ] `edoxen normalize-meetings PATTERN --output DIR` round-trips.
54
+ - [ ] Existing `validate` and `normalize` (for Resolutions) unchanged.
55
+ - [ ] `edoxen help` lists both pairs of commands.
@@ -0,0 +1,35 @@
1
+ # Phase 8 — Documentation
2
+
3
+ Priority: **P1**
4
+
5
+ ## Goal
6
+
7
+ README + CLAUDE.md reflect the Meeting/Agenda model. The canonical
8
+ edoxen-model README documents the new LUTAML files.
9
+
10
+ ## Deliverables — edoxen gem
11
+
12
+ - `README.adoc`:
13
+ - New "Meeting & Agenda model" section with ASCII tree.
14
+ - Quick-start example showing both Resolution and Meeting parsing.
15
+ - Cross-link to the canonical LUTAML files.
16
+ - `CLAUDE.md`:
17
+ - Architecture section mentions `Meeting`, `Agenda`, the meeting
18
+ schema, and the separate CLI subcommands.
19
+ - Audit checklist extended: schema_model_sync_spec covers both
20
+ `schema/edoxen.yaml` and `schema/meeting.yaml`.
21
+
22
+ ## Deliverables — edoxen-model
23
+
24
+ - `README.adoc`:
25
+ - File index extended with all new `.lutaml` files.
26
+ - New "Meeting model" section explaining the Meeting/Agenda split,
27
+ URN linking to Resolutions, and the localization pattern.
28
+ - Sample meeting YAML mirroring one of the gem's fixtures.
29
+
30
+ ## Acceptance criteria
31
+
32
+ - [ ] Both READMEs updated.
33
+ - [ ] edoxen CLAUDE.md updated.
34
+ - [ ] Sample YAML in edoxen-model README would pass
35
+ `edoxen validate-meetings`.
@@ -0,0 +1,41 @@
1
+ # Phase 9 — Verify, PR, merge, release
2
+
3
+ Priority: **P0**
4
+
5
+ ## Goal
6
+
7
+ Land everything via PRs. Verify GHA passes. Merge. Bump edoxen minor
8
+ (new feature) and release.
9
+
10
+ ## Branch / PR plan
11
+
12
+ | Repo | Branch | PRs against |
13
+ |---|---|---|
14
+ | `metanorma/edoxen-model` | `feat/meeting-agenda-model` | main |
15
+ | `metanorma/edoxen` | `feat/meeting-agenda-model` | main |
16
+
17
+ Order: edoxen-model first (canonical), then edoxen (mirror).
18
+
19
+ ## Verification gates
20
+
21
+ Per branch, before pushing:
22
+
23
+ - [ ] `bundle exec rspec` — 0 failures.
24
+ - [ ] `bundle exec rubocop` — 0 offenses.
25
+ - [ ] `bundle exec exe/edoxen validate-meetings "spec/fixtures/meetings/*.yaml"` — clean.
26
+ - [ ] `bundle exec exe/edoxen normalize-meetings --output /tmp/out` — round-trips.
27
+ - [ ] No AI attribution in any commit.
28
+ - [ ] No TODOs / FIXMEs in committed source.
29
+ - [ ] No transient files (.DS_Store, .bak, .swp).
30
+
31
+ ## Post-merge
32
+
33
+ - [ ] Trigger `gh workflow run release.yml --ref main -f next_version=minor`
34
+ on `metanorma/edoxen`.
35
+ - [ ] Confirm new version appears on rubygems.org.
36
+ - [ ] Confirm tag `vX.Y.Z` pushed.
37
+
38
+ ## Why minor (not patch)
39
+
40
+ The new Meeting/Agenda model is an additive feature. No breaking
41
+ change to existing Resolution APIs. Semver minor on the 0.x line.
@@ -0,0 +1,72 @@
1
+ # Phase 10 — Future enhancements (not in this release)
2
+
3
+ Priority: **P2**
4
+
5
+ These are documented as known-future-work; not executed in this pass.
6
+ Each one would be its own PR.
7
+
8
+ ## P2.1 — Per-schedule-item localization
9
+
10
+ `ScheduleItemLocalization` carrying per-language `event` and
11
+ `description`. Triggered when a real consumer needs WG meeting titles
12
+ in FR alongside EN.
13
+
14
+ ## P2.2 — Meeting series
15
+
16
+ `MeetingSeries` carrying `{identifier, name, description}` with
17
+ `Meeting.series_ref: String` (URN). Triggered when navigation across
18
+ ordinals becomes a real use case (ISO/TC 154 plenary history page).
19
+
20
+ ## P2.3 — Contribution model
21
+
22
+ Replace flat `Person.contributors` with a `Contribution` class
23
+ matching the comment in the original draft (`'contributors should
24
+ defer to Contribution model`). Triggered when we need to distinguish
25
+ "presented" vs "authored" vs "reviewed" contributions per meeting.
26
+
27
+ ## P2.4 — Auto-detect document type in CLI
28
+
29
+ A single `edoxen validate PATTERN` that auto-detects
30
+ ResolutionCollection vs Meeting via JSON-Schema `oneOf`. Triggered
31
+ when users complain about remembering two subcommand names.
32
+
33
+ ## P2.5 — Cross-document link validation
34
+
35
+ A spec/assertion that every `Meeting.resolution_refs[*]` URN has a
36
+ matching `ResolutionCollection` somewhere in the data set, and vice
37
+ versa. Triggered when broken links cause real bugs.
38
+
39
+ ## P2.6 — Attendance roster
40
+
41
+ `Meeting.attendance: Attendance[]` carrying per-person attendance
42
+ records (present/absent/apologies + affiliation). Triggered when a
43
+ consumer needs the full attendance record, not just officers.
44
+
45
+ ## P2.7 — Vote records
46
+
47
+ `VoteRecord { resolution_ref, person, vote: VoteType }` linked to a
48
+ Meeting. Triggered when ballot-by-ballot transparency becomes a
49
+ requirement.
50
+
51
+ ## P2.8 — Minutes narrative
52
+
53
+ A `Minutes` class distinct from `Agenda`, capturing the actual
54
+ narrative of what was said (linked Markdown / Asciidoc blocks per
55
+ agenda item). Triggered when the OIML `meetings/` directory starts
56
+ receiving Bulletin scans.
57
+
58
+ ## P2.9 — federation (multi-body)
59
+
60
+ A `Federation` or `JointMeeting` class for meetings organized by
61
+ multiple bodies (e.g., a JWG with TC 154 + TC 307). Triggered when
62
+ real joint-meeting data lands.
63
+
64
+ ## P2.10 — IANA / ISO 3166-1 / ISO 639-3 / ISO 15924 reference data
65
+
66
+ Built-in lookup tables to validate `country_code`, `language_code`,
67
+ `script` against the actual standard. Triggered when typo-rate
68
+ becomes painful.
69
+
70
+ ## Acceptance criteria
71
+
72
+ This file documents intent only; nothing to land in this release.
@@ -0,0 +1,30 @@
1
+ # TODO.meeting-agenda — index
2
+
3
+ The work to land a canonical Meeting + Agenda + AgendaItem model
4
+ linkable to Resolutions/Decisions across `edoxen` and `edoxen-model`.
5
+
6
+ ## Phases
7
+
8
+ | # | File | Priority | Status |
9
+ |---|---|---|---|
10
+ | 1 | [01-design-decisions.md](01-design-decisions.md) | P0 | done |
11
+ | 2 | [02-lutaml-canonical.md](02-lutaml-canonical.md) | P0 | pending |
12
+ | 3 | [03-ruby-models.md](03-ruby-models.md) | P0 | pending |
13
+ | 4 | [04-schema.md](04-schema.md) | P0 | pending |
14
+ | 5 | [05-fixtures.md](05-fixtures.md) | P0 | pending |
15
+ | 6 | [06-specs.md](06-specs.md) | P0 | pending |
16
+ | 7 | [07-cli.md](07-cli.md) | P1 | pending |
17
+ | 8 | [08-docs.md](08-docs.md) | P1 | pending |
18
+ | 9 | [09-verify-and-release.md](09-verify-and-release.md) | P0 | pending |
19
+ | 10 | [10-future-enhancements.md](10-future-enhancements.md) | P2 | documented |
20
+
21
+ ## Execution order
22
+
23
+ Phases 2 → 9 are executed in order. Phase 10 documents known-future-
24
+ work only.
25
+
26
+ ## Branches
27
+
28
+ - `metanorma/edoxen-model` ← `feat/meeting-agenda-model` (Phase 2)
29
+ - `metanorma/edoxen` ← `feat/meeting-agenda-model` (Phases 3–8)
30
+ - Phase 9 merges both, then releases `edoxen` as a minor bump.
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # The business-order document of a Meeting. Distinct from the
5
+ # timetable (Schedule) — the Agenda orders topics; the Schedule
6
+ # orders time slots.
7
+ #
8
+ # An Agenda may be versioned independently of the Meeting: a draft
9
+ # agenda circulates weeks before; a final agenda at meeting time;
10
+ # an amended agenda if items are added during the meeting. The
11
+ # `status` field captures that lifecycle.
12
+ class Agenda < Lutaml::Model::Serializable
13
+ attribute :identifier, StructuredIdentifier, collection: true
14
+ attribute :status, :string, values: Enums::AGENDA_STATUS
15
+ attribute :source_doc, :string
16
+ attribute :items, AgendaItem, collection: true
17
+ attribute :opening_session, ScheduleItem
18
+ attribute :closing_session, ScheduleItem
19
+
20
+ # Find an agenda item by its label (e.g., "5.2"). Returns nil
21
+ # when no item matches.
22
+ def find_item(label)
23
+ items&.find { |item| item.label == label.to_s }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # One entry on an Agenda. `label` is the visible identifier (e.g.,
5
+ # "5.2"); `kind` discriminates numbered/unnumbered/header/opening/
6
+ # closing; `outcome` records what happened; `resolution_ref`
7
+ # optionally links to the URN of the resolution this item produced.
8
+ class AgendaItem < Lutaml::Model::Serializable
9
+ attribute :label, :string
10
+ attribute :kind, :string, values: Enums::AGENDA_ITEM_KIND
11
+ attribute :title, :string
12
+ attribute :description, :string
13
+ attribute :references, Reference, collection: true
14
+ attribute :outcome, :string, values: Enums::AGENDA_ITEM_OUTCOME
15
+ attribute :resolution_ref, :string
16
+ end
17
+ end
data/lib/edoxen/cli.rb CHANGED
@@ -125,8 +125,53 @@ module Edoxen
125
125
  end
126
126
  end
127
127
 
128
+ desc "validate-meetings YAML_FILE_PATTERN",
129
+ "Validate Meeting/Agenda YAML file(s) against schema/meeting.yaml."
130
+
131
+ def validate_meetings(pattern)
132
+ validator = SchemaValidator.new(meeting_schema_path)
133
+ Batch.run(self, pattern, header: "🔍 Validating meetings") do |file|
134
+ schema_errors = validator.validate_file(file)
135
+ model_errors = collect_meeting_model_errors(file)
136
+ if schema_errors.empty? && model_errors.empty?
137
+ Batch::Result.ok("VALID")
138
+ else
139
+ errors = (schema_errors + model_errors).map(&:to_clickable_format)
140
+ Batch::Result.bad(errors)
141
+ end
142
+ end
143
+ end
144
+
145
+ desc "normalize-meetings YAML_FILE_PATTERN",
146
+ "Round-trip Meeting YAML file(s) through the model (--output DIR or --inplace)."
147
+
148
+ option :output, type: :string, desc: "Output directory for normalized files"
149
+ option :inplace, type: :boolean, desc: "Modify files in place (no backup)"
150
+
151
+ def normalize_meetings(pattern)
152
+ unless valid_normalize_options?
153
+ say normalize_options_error, :red
154
+ exit 1
155
+ end
156
+
157
+ summary_extra = [
158
+ [" Output directory", options[:output]],
159
+ [" Mode", options[:inplace] ? "in place" : "--output"]
160
+ ].compact
161
+
162
+ Batch.run(self, pattern, header: "🔄 Normalizing meetings", summary_extra: summary_extra) do |file|
163
+ Batch::Result.ok(normalize_meeting_file(file))
164
+ rescue StandardError => e
165
+ Batch::Result.bad(["#{file}:1:1: #{e.message}"])
166
+ end
167
+ end
168
+
128
169
  private
129
170
 
171
+ def meeting_schema_path
172
+ File.expand_path("../../schema/meeting.yaml", __dir__)
173
+ end
174
+
130
175
  def collect_model_errors(file)
131
176
  ResolutionCollection.from_yaml(File.read(file))
132
177
  []
@@ -138,6 +183,30 @@ module Edoxen
138
183
  )]
139
184
  end
140
185
 
186
+ def collect_meeting_model_errors(file)
187
+ load_meeting_document(file)
188
+ []
189
+ rescue StandardError => e
190
+ [Edoxen::ValidationError.new(
191
+ file: file, line: 1, column: 1,
192
+ message_text: "Meeting model parsing failed: #{e.message}",
193
+ source: Edoxen::ValidationError::SOURCE_MODEL
194
+ )]
195
+ end
196
+
197
+ # A meeting YAML may be either a single Meeting or a
198
+ # MeetingCollection. Detect by the presence of a top-level
199
+ # `meetings:` key and parse accordingly. Returns whichever
200
+ # model object was successfully constructed.
201
+ def load_meeting_document(file)
202
+ data = YAML.safe_load(File.read(file), permitted_classes: [Date, Time])
203
+ if data.is_a?(Hash) && data.key?("meetings")
204
+ MeetingCollection.from_yaml(File.read(file))
205
+ else
206
+ Meeting.from_yaml(File.read(file))
207
+ end
208
+ end
209
+
141
210
  def extract_yaml_language_server_comment(content)
142
211
  lines = content.split("\n").first(5)
143
212
  lines.find { |l| l.strip.match?(/\A#\s*yaml-language-server:\s*\$schema=/) }&.rstrip
@@ -165,6 +234,16 @@ module Edoxen
165
234
  original = File.read(file)
166
235
  yaml_language_server_comment = extract_yaml_language_server_comment(original)
167
236
  normalized = ResolutionCollection.from_yaml(original).to_yaml
237
+ write_normalized(file, normalized, yaml_language_server_comment)
238
+ end
239
+
240
+ def normalize_meeting_file(file)
241
+ yaml_language_server_comment = extract_yaml_language_server_comment(File.read(file))
242
+ normalized = load_meeting_document(file).to_yaml
243
+ write_normalized(file, normalized, yaml_language_server_comment)
244
+ end
245
+
246
+ def write_normalized(file, normalized, yaml_language_server_comment)
168
247
  normalized = "#{yaml_language_server_comment}\n#{normalized}" if yaml_language_server_comment
169
248
 
170
249
  if options[:inplace]
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Start + end pair for multi-day meetings. Both endpoints are Date.
5
+ # Either may be nil for one-sided ranges (theoretically), though the
6
+ # schema enforces both required when DateRange is present.
7
+ class DateRange < Lutaml::Model::Serializable
8
+ attribute :start, :date
9
+ attribute :end, :date
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # A time-bound requirement leading up to (or following) a meeting —
5
+ # registration cutoff, report-submission deadline, etc.
6
+ class Deadline < Lutaml::Model::Serializable
7
+ attribute :date, :date
8
+ attribute :description, :string
9
+ end
10
+ end
data/lib/edoxen/enums.rb CHANGED
@@ -39,5 +39,29 @@ module Edoxen
39
39
  APPROVAL_DEGREE = %w[unanimous majority minority].freeze
40
40
 
41
41
  URL_KIND = %w[access report].freeze
42
+
43
+ # --- Meeting/Agenda side ----------------------------------------------
44
+ # Mirrors edoxen-model/models/{meeting,agenda,...}_*.lutaml.
45
+ # schema/meeting.yaml references these via $defs/<EnumName>.enum.
46
+
47
+ MEETING_TYPE = %w[plenary working_group task_group ad_hoc joint conference_session].freeze
48
+
49
+ MEETING_STATUS = %w[upcoming completed cancelled].freeze
50
+
51
+ AGENDA_STATUS = %w[draft final amended cancelled superseded].freeze
52
+
53
+ AGENDA_ITEM_KIND = %w[numbered unnumbered header opening closing].freeze
54
+
55
+ AGENDA_ITEM_OUTCOME = %w[discussed resolved deferred adopted withdrawn].freeze
56
+
57
+ HOST_TYPE = %w[national_body liaison associate organizer].freeze
58
+
59
+ MEETING_RELATION_TYPE = %w[
60
+ continues_from continues_to joint_with supersedes superseded_by rescheduled_from rescheduled_to
61
+ ].freeze
62
+
63
+ SOURCE_URL_KIND = %w[
64
+ agenda_pdf minutes_pdf resolutions_pdf report_pdf register_url landing_page
65
+ ].freeze
42
66
  end
43
67
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Typed reference to an organization that hosts or co-organizes a
5
+ # meeting. `ref` is a stable identifier into an external registry
6
+ # (national_bodies.yml, liaisons.yml, associates.yml in ISO/TC 154);
7
+ # `type` discriminates which registry.
8
+ class HostRef < Lutaml::Model::Serializable
9
+ attribute :ref, :string
10
+ attribute :type, :string, values: Enums::HOST_TYPE
11
+ attribute :role, :string
12
+ attribute :contact, Person
13
+ end
14
+ 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,68 @@
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 :localizations, MeetingLocalization, collection: true
46
+ attribute :relations, MeetingRelation, collection: true
47
+ attribute :resolution_refs, :string, collection: true
48
+
49
+ # Lookup by ISO 639-3 language code. Mirrors Resolution#in_language.
50
+ def in_language(code, fallback: false)
51
+ match = localizations&.find { |loc| loc.language_code == code.to_s }
52
+ return match if match
53
+
54
+ fallback ? localizations&.first : nil
55
+ end
56
+
57
+ # English when available, else the first declared localization.
58
+ def primary_localization
59
+ in_language("eng", fallback: true)
60
+ end
61
+
62
+ # Find an agenda item by label. Returns nil when no agenda or no
63
+ # matching label.
64
+ def find_agenda_item(label)
65
+ agenda&.find_item(label)
66
+ end
67
+ end
68
+ 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,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
@@ -12,5 +12,11 @@ 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
15
21
  end
16
22
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # One entry in a meeting timetable. Structural fields (date, time,
5
+ # room) are language-agnostic; descriptive fields (event,
6
+ # description) live here as plain strings in v1 — see
7
+ # TODO.meeting-agenda/10-future-enhancements.md P2.1 for per-item
8
+ # localization.
9
+ class ScheduleItem < Lutaml::Model::Serializable
10
+ attribute :date, :date
11
+ attribute :time, :string
12
+ attribute :event, :string
13
+ attribute :description, :string
14
+ attribute :room, :string
15
+ end
16
+ end