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.
- checksums.yaml +4 -4
- data/README.adoc +170 -31
- data/TODO.meeting-agenda/01-design-decisions.md +132 -0
- data/TODO.meeting-agenda/02-lutaml-canonical.md +61 -0
- data/TODO.meeting-agenda/03-ruby-models.md +73 -0
- data/TODO.meeting-agenda/04-schema.md +34 -0
- data/TODO.meeting-agenda/05-fixtures.md +43 -0
- data/TODO.meeting-agenda/06-specs.md +52 -0
- data/TODO.meeting-agenda/07-cli.md +55 -0
- data/TODO.meeting-agenda/08-docs.md +35 -0
- data/TODO.meeting-agenda/09-verify-and-release.md +41 -0
- data/TODO.meeting-agenda/10-future-enhancements.md +72 -0
- data/TODO.meeting-agenda/README.md +30 -0
- data/lib/edoxen/agenda.rb +26 -0
- data/lib/edoxen/agenda_item.rb +17 -0
- data/lib/edoxen/cli.rb +79 -0
- data/lib/edoxen/date_range.rb +11 -0
- data/lib/edoxen/deadline.rb +10 -0
- data/lib/edoxen/enums.rb +24 -0
- data/lib/edoxen/host_ref.rb +14 -0
- data/lib/edoxen/location.rb +15 -0
- data/lib/edoxen/meeting.rb +68 -0
- data/lib/edoxen/meeting_collection.rb +26 -0
- data/lib/edoxen/meeting_collection_metadata.rb +11 -0
- data/lib/edoxen/meeting_localization.rb +15 -0
- data/lib/edoxen/meeting_relation.rb +12 -0
- data/lib/edoxen/person.rb +14 -0
- data/lib/edoxen/reference.rb +13 -0
- data/lib/edoxen/resolution_metadata.rb +6 -0
- data/lib/edoxen/schedule_item.rb +16 -0
- data/lib/edoxen/source_url.rb +6 -2
- data/lib/edoxen/version.rb +1 -1
- data/lib/edoxen.rb +16 -0
- data/schema/edoxen.yaml +7 -0
- data/schema/meeting.yaml +358 -0
- metadata +27 -2
|
@@ -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
|
data/lib/edoxen/meeting.rb
CHANGED
|
@@ -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
|