skilldrop-cli 1.6.0 → 1.7.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.
- package/README.md +2 -0
- package/catalogue.json +18 -2
- package/model-routing.json +16 -0
- package/package.json +1 -1
- package/packs/event-planner/pack.json +61 -0
- package/packs/event-planner/skills/run-of-show/SKILL.md +141 -0
- package/packs/event-planner/skills/run-of-show/evals/eval_queries.json +30 -0
- package/packs/event-planner/skills/run-of-show/evals/evals.json +103 -0
- package/packs/event-planner/skills/run-of-show/evals/files/1/agenda.md +16 -0
- package/packs/event-planner/skills/run-of-show/evals/files/2/schedule.csv +8 -0
- package/packs/event-planner/skills/run-of-show/evals/files/3/notes.txt +3 -0
- package/packs/event-planner/skills/run-of-show/evals/files/4/sessions_export.csv +25 -0
- package/packs/event-planner/skills/run-of-show/evals/files/4/venue_and_crew.txt +27 -0
- package/packs/event-planner/skills/run-of-show/evals/files/5/runsheet_v4.csv +28 -0
- package/packs/event-planner/skills/run-of-show/evals/files/5/venue_and_suppliers.txt +22 -0
- package/packs/event-planner/skills/run-of-show/evals/files/6/acts_from_booker.csv +26 -0
- package/packs/event-planner/skills/run-of-show/evals/files/6/venue_rooms_and_crew.txt +14 -0
- package/packs/event-planner/skills/run-of-show/evals/files/7/runsheet_draft_v4.csv +25 -0
- package/packs/event-planner/skills/run-of-show/evals/files/7/venue_and_fixed_times.txt +24 -0
- package/packs/event-planner/skills/run-of-show/examples/product-launch.md +77 -0
- package/packs/event-planner/skills/run-of-show/examples/product-launch.svg +58 -0
- package/packs/event-planner/skills/run-of-show/manifest.json +44 -0
- package/packs/event-planner/skills/run-of-show/scripts/show_check.py +295 -0
- package/packs/event-planner/skills/run-of-show/templates/show.json +15 -0
- package/packs/event-planner/skills/seating-plan/SKILL.md +152 -0
- package/packs/event-planner/skills/seating-plan/evals/eval_queries.json +30 -0
- package/packs/event-planner/skills/seating-plan/evals/evals.json +134 -0
- package/packs/event-planner/skills/seating-plan/evals/files/1/guests.csv +31 -0
- package/packs/event-planner/skills/seating-plan/evals/files/2/table-plan.txt +8 -0
- package/packs/event-planner/skills/seating-plan/evals/files/3/brief.txt +4 -0
- package/packs/event-planner/skills/seating-plan/evals/files/3/guests.csv +49 -0
- package/packs/event-planner/skills/seating-plan/evals/files/4/rsvp_export.csv +127 -0
- package/packs/event-planner/skills/seating-plan/evals/files/4/seating_notes.txt +21 -0
- package/packs/event-planner/skills/seating-plan/evals/files/5/organiser_notes.txt +10 -0
- package/packs/event-planner/skills/seating-plan/evals/files/5/registrations.csv +93 -0
- package/packs/event-planner/skills/seating-plan/evals/files/5/table_plan_v3.csv +94 -0
- package/packs/event-planner/skills/seating-plan/evals/files/6/bridgets_notes.txt +30 -0
- package/packs/event-planner/skills/seating-plan/evals/files/6/ticket_export.csv +117 -0
- package/packs/event-planner/skills/seating-plan/evals/files/6/venue_floorplan_notes.txt +14 -0
- package/packs/event-planner/skills/seating-plan/evals/files/7/keep_apart_CONFIDENTIAL.txt +9 -0
- package/packs/event-planner/skills/seating-plan/evals/files/7/prom_seating_v3.csv +101 -0
- package/packs/event-planner/skills/seating-plan/evals/files/7/ticket_list.csv +101 -0
- package/packs/event-planner/skills/seating-plan/evals/files/7/venue_info.txt +16 -0
- package/packs/event-planner/skills/seating-plan/evals/files/8/registrations_export.csv +147 -0
- package/packs/event-planner/skills/seating-plan/evals/files/8/room_layout.md +22 -0
- package/packs/event-planner/skills/seating-plan/evals/files/8/seating_requests.txt +31 -0
- package/packs/event-planner/skills/seating-plan/evals/files/9/guest_list.csv +84 -0
- package/packs/event-planner/skills/seating-plan/evals/files/9/notes.txt +16 -0
- package/packs/event-planner/skills/seating-plan/evals/files/9/table_plan.csv +85 -0
- package/packs/event-planner/skills/seating-plan/examples/wedding-dinner.md +61 -0
- package/packs/event-planner/skills/seating-plan/examples/wedding-dinner.svg +39 -0
- package/packs/event-planner/skills/seating-plan/manifest.json +44 -0
- package/packs/event-planner/skills/seating-plan/scripts/seating_check.py +324 -0
- package/packs/event-planner/skills/seating-plan/templates/seating.json +28 -0
- package/packs/experience-design/pack.json +5 -0
- package/packs/experience-design/skills/locale-check/SKILL.md +139 -0
- package/packs/experience-design/skills/locale-check/evals/eval_queries.json +38 -0
- package/packs/experience-design/skills/locale-check/evals/evals.json +104 -0
- package/packs/experience-design/skills/locale-check/evals/files/1/budget.json +1 -0
- package/packs/experience-design/skills/locale-check/evals/files/1/locales/ar.json +16 -0
- package/packs/experience-design/skills/locale-check/evals/files/1/locales/de.json +12 -0
- package/packs/experience-design/skills/locale-check/evals/files/1/locales/en.json +12 -0
- package/packs/experience-design/skills/locale-check/evals/files/1/locales/pl.json +12 -0
- package/packs/experience-design/skills/locale-check/evals/files/2/app/src/main/res/values/strings.xml +12 -0
- package/packs/experience-design/skills/locale-check/evals/files/2/app/src/main/res/values-es/strings.xml +11 -0
- package/packs/experience-design/skills/locale-check/evals/files/2/app/src/main/res/values-fr/strings.xml +12 -0
- package/packs/experience-design/skills/locale-check/evals/files/3/po/ja.po +20 -0
- package/packs/experience-design/skills/locale-check/evals/files/3/po/messages.pot +23 -0
- package/packs/experience-design/skills/locale-check/evals/files/3/po/ru.po +22 -0
- package/packs/experience-design/skills/locale-check/evals/files/4/budgets.json +15 -0
- package/packs/experience-design/skills/locale-check/evals/files/4/locales/ar.json +265 -0
- package/packs/experience-design/skills/locale-check/evals/files/4/locales/de.json +265 -0
- package/packs/experience-design/skills/locale-check/evals/files/4/locales/en.json +265 -0
- package/packs/experience-design/skills/locale-check/evals/files/4/locales/fr.json +265 -0
- package/packs/experience-design/skills/locale-check/evals/files/4/locales/pl.json +265 -0
- package/packs/experience-design/skills/locale-check/evals/files/5/app/src/main/res/values/strings.xml +77 -0
- package/packs/experience-design/skills/locale-check/evals/files/5/app/src/main/res/values-es/strings.xml +63 -0
- package/packs/experience-design/skills/locale-check/evals/files/5/app/src/main/res/values-ja/strings.xml +61 -0
- package/packs/experience-design/skills/locale-check/evals/files/6/po/cs.po +605 -0
- package/packs/experience-design/skills/locale-check/evals/files/6/po/marlin.pot +602 -0
- package/packs/experience-design/skills/locale-check/evals/files/6/po/pt_BR.po +602 -0
- package/packs/experience-design/skills/locale-check/evals/files/6/po/ru.po +608 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/en.lproj/Localizable.strings +160 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/en.lproj/Localizable.stringsdict +70 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/he.lproj/Localizable.strings +157 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/he.lproj/Localizable.stringsdict +86 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/it.lproj/Localizable.strings +160 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/it.lproj/Localizable.stringsdict +68 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/ko.lproj/Localizable.strings +160 -0
- package/packs/experience-design/skills/locale-check/evals/files/7/Tidewatch/Resources/ko.lproj/Localizable.stringsdict +62 -0
- package/packs/experience-design/skills/locale-check/examples/settings-release.md +107 -0
- package/packs/experience-design/skills/locale-check/manifest.json +45 -0
- package/packs/experience-design/skills/locale-check/scripts/locale_check.py +1105 -0
- package/packs/operations-manager/pack.json +60 -0
- package/packs/operations-manager/skills/shift-roster/SKILL.md +166 -0
- package/packs/operations-manager/skills/shift-roster/evals/eval_queries.json +38 -0
- package/packs/operations-manager/skills/shift-roster/evals/evals.json +135 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/1/shifts.csv +3 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/1/staff.csv +8 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/2/rota.csv +22 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/2/staff.csv +6 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/3/team.md +14 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/4/availability_export_wk47.csv +23 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/4/rota_notes.txt +20 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/5/rota_draft_16-29nov.csv +107 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/5/staff_list.csv +17 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/6/availability_notes.txt +24 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/6/peoplehub_staff_export.csv +14 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/6/rota_template.csv +25 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/7/leave_and_availability.txt +18 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/7/rota_wc_30nov.csv +47 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/7/staff_list.csv +15 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/8/availability_form_responses.csv +22 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/8/cover_requirements.csv +15 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/8/staff_export.csv +29 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/9/leave_and_restrictions.txt +16 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/9/rota_export_07-13Dec.csv +85 -0
- package/packs/operations-manager/skills/shift-roster/evals/files/9/staff_list.csv +27 -0
- package/packs/operations-manager/skills/shift-roster/examples/cafe-week.md +72 -0
- package/packs/operations-manager/skills/shift-roster/examples/cafe-week.svg +123 -0
- package/packs/operations-manager/skills/shift-roster/manifest.json +34 -0
- package/packs/operations-manager/skills/shift-roster/scripts/roster_check.py +633 -0
- package/packs/operations-manager/skills/shift-roster/templates/roster.json +22 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Start,End,Room,Item,Speakers / talent,Crew needed,Notes
|
|
2
|
+
08:00,09:30,Hall,Tech rehearsal,Imran Kestrel; Ana Ruiz; Gemma Thorne,"AV team x3, stage manager",
|
|
3
|
+
09:00,10:00,Terrace,Arrival coffee,,Catering,
|
|
4
|
+
10:00,10:40,Hall,Welcome & keynote,Imran Kestrel; Gemma Thorne,"AV team x3, stage manager, camera x2",embargo lifts 10:00
|
|
5
|
+
10:30,10:50,Green Room,Press interview - Tech Weekly,Imran Kestrel,Press officer,
|
|
6
|
+
10:40,17:30,Atrium,Demo zone (Lumio Fold 2 hands-on),,Demo crew x4,
|
|
7
|
+
10:50,11:30,Hall,Product demo,Ana Ruiz,"AV team x3, stage manager, camera x2",
|
|
8
|
+
10:55,11:15,Green Room,Press interview - Retail Insider,Owen Strand,Press officer,
|
|
9
|
+
11:00,11:45,Studio B,Developer breakout,Tom Vickers; Hana Sato,AV x1,
|
|
10
|
+
11:30,12:15,Studio B,Retail partners briefing,Owen Strand; Ines Duarte,AV x1,
|
|
11
|
+
11:50,12:10,Green Room,Press interview - Gadget Desk,Ana Ruiz,Press officer,
|
|
12
|
+
12:00,12:45,Hall,Customer stories panel,Gemma Thorne; Ifeoma Ndukwe; Lars Pettersen; Carys Bowen,"AV team x3, stage manager",
|
|
13
|
+
12:15,12:35,Green Room,Press interview - DevWire,Tom Vickers,Press officer,
|
|
14
|
+
12:45,13:40,Terrace,Lunch,,Catering,caterer can only do 12:45
|
|
15
|
+
14:00,14:45,Studio B,Accessibility features deep-dive,Hana Sato,AV x1,
|
|
16
|
+
14:00,15:00,Hall,Lumio Live (livestream),Imran Kestrel; Ana Ruiz; Gemma Thorne,"Pixelhaus broadcast crew, AV team x3, stage manager",FIXED - broadcast partner slot
|
|
17
|
+
15:00,15:45,Studio B,Enterprise security briefing,Tom Vickers,AV x1,
|
|
18
|
+
15:15,15:35,Green Room,Press interview - Business Daily,Imran Kestrel,Press officer,
|
|
19
|
+
15:20,16:20,Hall,Partner panel,Gemma Thorne; Rohan Mehta; Ines Duarte,"AV team x3, stage manager",
|
|
20
|
+
15:40,16:00,Green Room,Press interview - Mobile Matters,Ana Ruiz,Press officer,
|
|
21
|
+
16:00,16:45,Studio B,Partner onboarding clinic,Owen Strand,AV x1,
|
|
22
|
+
16:05,16:25,Green Room,Press interview - Access Today,Hana Sato,Press officer,
|
|
23
|
+
16:40,17:25,Hall,Engineering Q&A,Tom Vickers; Ana Ruiz,"AV team x3, stage manager",
|
|
24
|
+
17:45,18:45,Hall,Closing & customer awards,Imran Kestrel; Gemma Thorne,"AV team x3, stage manager, camera x2",6 awards
|
|
25
|
+
18:00,18:45,Terrace,Drinks reception,,"Catering, bar",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Lumio Fold 2 launch - Thu 21 Jan 2027 - The Granary Rooms
|
|
2
|
+
(from the venue contract + our own fixed points)
|
|
3
|
+
|
|
4
|
+
VENUE
|
|
5
|
+
- Crew access from 08:00. Guests arrive 09:00.
|
|
6
|
+
- The HALL must be completely clear by 18:00 - the venue has an evening booking getting in from 18:00. No flex, it's in the contract.
|
|
7
|
+
- Studio B, Green Room and Atrium available until 18:30; Terrace until 19:00 (building closes 19:00).
|
|
8
|
+
- Hall seats 300 (all guests). Studio B seats 80. Green Room is for press only (holds 6).
|
|
9
|
+
|
|
10
|
+
TURNAROUND between sessions
|
|
11
|
+
- Hall: 20 min stage reset between sessions (AV company's minimum)
|
|
12
|
+
- Studio B: 10 min
|
|
13
|
+
- Green Room: 5 min between interviews
|
|
14
|
+
- Atrium/Terrace: n/a (catering + demo zone)
|
|
15
|
+
|
|
16
|
+
PEOPLE
|
|
17
|
+
- Speakers need at least 10 min to get from one room to another (and be mic'd).
|
|
18
|
+
|
|
19
|
+
FIXED - CAN'T MOVE
|
|
20
|
+
- Keynote at 10:00 (press embargo lifts 10:00, it's on the invite)
|
|
21
|
+
- Hall tech rehearsal until 09:30
|
|
22
|
+
- Lumio Live livestream in the Hall 14:00-15:00 (broadcast partner's slot)
|
|
23
|
+
- Lunch on the Terrace 12:45-13:40 (caterer)
|
|
24
|
+
- All Hall sessions are for the whole audience of 300, so they have to stay in the Hall
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Worked example: a product launch day
|
|
2
|
+
|
|
3
|
+
Input → output oracle for `run-of-show`. Shows an agenda that reads fine and has four clashes:
|
|
4
|
+
a keynote's stage setup colliding with doors in the same room, two handovers shorter than the
|
|
5
|
+
room needs, and the CEO due in the demo lab the minute her keynote ends.
|
|
6
|
+
|
|
7
|
+
## Input
|
|
8
|
+
|
|
9
|
+
> Here's the launch day. Auditorium 8:00–18:00, needs 20 minutes to turn over between
|
|
10
|
+
> sessions; the demo lab is 9:00–17:00, 10 minutes. It's a five-minute walk between them, and
|
|
11
|
+
> we've told people we finish at 17:00.
|
|
12
|
+
>
|
|
13
|
+
> - 8:30–9:00 doors and coffee, auditorium
|
|
14
|
+
> - 9:00–9:45 launch keynote, Mara (CEO); AV needs 30 minutes on stage before it
|
|
15
|
+
> - 9:45–10:30 live demo of the setup flow, demo lab, Mara and Theo
|
|
16
|
+
> - 10:00–11:00 customer panel, auditorium, Ines hosting; the stage takes 25 minutes to strike after
|
|
17
|
+
> - 11:15–12:15 lunch, auditorium
|
|
18
|
+
> - 13:00–15:30 hands-on lab, demo lab, Theo
|
|
19
|
+
> - 16:45–17:15 closing remarks, Mara
|
|
20
|
+
|
|
21
|
+
## The agenda as given, checked
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
FAIL Auditorium: 0 min between Doors and coffee (08:30) and Launch keynote (09:00), 30 min needed for Launch keynote's setup
|
|
25
|
+
FAIL Auditorium: 15 min between Launch keynote (09:00) and Customer panel (10:00), 20 min needed for the room's turnaround
|
|
26
|
+
FAIL Auditorium: 15 min between Customer panel (10:00) and Lunch (11:15), 25 min needed for Customer panel's changeover
|
|
27
|
+
FAIL Mara Quinn (CEO) has 0 min to get from Auditorium to Demo lab between Launch keynote and Live demo: setup flow; the walk takes 5
|
|
28
|
+
WARN Auditorium is empty for 270 min between Lunch and Closing remarks
|
|
29
|
+
WARN Demo lab is empty for 150 min between Live demo: setup flow and Hands-on lab
|
|
30
|
+
WARN Closing remarks ends at 17:15, 15 min after the published end of 17:00
|
|
31
|
+
4 failure(s)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Each one has a small fix that moves as little as possible. Doors and coffee go to the foyer,
|
|
35
|
+
so the stage crew has the auditorium from 8:30. The demo starts five minutes later, so Mara
|
|
36
|
+
can walk over. The panel moves 5 minutes and lunch 15, so each handover gets the time the
|
|
37
|
+
room needs. Closing starts 15 minutes earlier, to end on time.
|
|
38
|
+
|
|
39
|
+
## The reply
|
|
40
|
+
|
|
41
|
+
**No room or person is booked twice, and the day ends at 17:00 as published.** Five times
|
|
42
|
+
moved from your agenda, each by 15 minutes or less.
|
|
43
|
+
|
|
44
|
+
| Time | Room | Item | Who's needed |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| 08:30–09:00 | Foyer | Doors and coffee | Front of house |
|
|
47
|
+
| 09:00–09:45 | Auditorium | Launch keynote | Mara, AV crew (on stage from 08:30) |
|
|
48
|
+
| 09:50–10:35 | Demo lab | Live demo: setup flow | Mara, Theo |
|
|
49
|
+
| 10:05–11:05 | Auditorium | Customer panel | Ines, AV crew |
|
|
50
|
+
| 11:30–12:30 | Auditorium | Lunch | |
|
|
51
|
+
| 13:00–15:30 | Demo lab | Hands-on lab | Theo |
|
|
52
|
+
| 16:30–17:00 | Auditorium | Closing remarks | Mara |
|
|
53
|
+
|
|
54
|
+
**What moved, and why:**
|
|
55
|
+
- **Doors and coffee to the foyer.** The keynote stage needs 30 minutes and the room opened
|
|
56
|
+
for coffee at 8:30.
|
|
57
|
+
- **Live demo to 09:50.** Mara's keynote ends at 09:45, and the lab is a five-minute walk.
|
|
58
|
+
- **Panel to 10:05, lunch to 11:30.** The auditorium needs 20 minutes between sessions, and 25
|
|
59
|
+
after the panel to strike the stage.
|
|
60
|
+
- **Closing remarks to 16:30.** They ended at 17:15, after the time you've published.
|
|
61
|
+
|
|
62
|
+
**Assumption:** `[assumption]` the foyer is free from 08:00. If it isn't, the keynote moves to
|
|
63
|
+
09:30 instead, and everything after it moves 30 minutes.
|
|
64
|
+
|
|
65
|
+
The auditorium sits empty from 12:30 to 16:30 while the hands-on lab runs. That's fine if
|
|
66
|
+
everyone is in the lab; if not, it's the obvious slot for a second session.
|
|
67
|
+
|
|
68
|
+
The timeline, one lane per room, is in `run-of-show.svg`.
|
|
69
|
+
|
|
70
|
+
## Why this is the oracle
|
|
71
|
+
|
|
72
|
+
- **Setup, turnaround and changeover are checked as well as overlaps.** A plain read sees no
|
|
73
|
+
two sessions at the same time in one room, and calls the agenda clash-free.
|
|
74
|
+
- **People are checked across rooms, with the walk.** The CEO's back-to-back across two rooms
|
|
75
|
+
is the clash crews find on the day.
|
|
76
|
+
- **Each fix is the smallest move, and the reply lists them.** It doesn't silently re-time the
|
|
77
|
+
day or squeeze the stage strike to fit.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 316" width="1280" height="316" font-family="Helvetica, Arial, sans-serif">
|
|
2
|
+
<rect width="1280" height="316" fill="#ffffff"/>
|
|
3
|
+
<text x="20" y="28" font-size="16" font-weight="bold">Run of show</text>
|
|
4
|
+
<line x1="150.0" y1="60" x2="150.0" y2="252" stroke="#cfd8dc"/>
|
|
5
|
+
<text x="150.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">08:00</text>
|
|
6
|
+
<line x1="205.0" y1="60" x2="205.0" y2="252" stroke="#eceff1"/>
|
|
7
|
+
<line x1="260.0" y1="60" x2="260.0" y2="252" stroke="#cfd8dc"/>
|
|
8
|
+
<text x="260.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">09:00</text>
|
|
9
|
+
<line x1="315.0" y1="60" x2="315.0" y2="252" stroke="#eceff1"/>
|
|
10
|
+
<line x1="370.0" y1="60" x2="370.0" y2="252" stroke="#cfd8dc"/>
|
|
11
|
+
<text x="370.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">10:00</text>
|
|
12
|
+
<line x1="425.0" y1="60" x2="425.0" y2="252" stroke="#eceff1"/>
|
|
13
|
+
<line x1="480.0" y1="60" x2="480.0" y2="252" stroke="#cfd8dc"/>
|
|
14
|
+
<text x="480.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">11:00</text>
|
|
15
|
+
<line x1="535.0" y1="60" x2="535.0" y2="252" stroke="#eceff1"/>
|
|
16
|
+
<line x1="590.0" y1="60" x2="590.0" y2="252" stroke="#cfd8dc"/>
|
|
17
|
+
<text x="590.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">12:00</text>
|
|
18
|
+
<line x1="645.0" y1="60" x2="645.0" y2="252" stroke="#eceff1"/>
|
|
19
|
+
<line x1="700.0" y1="60" x2="700.0" y2="252" stroke="#cfd8dc"/>
|
|
20
|
+
<text x="700.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">13:00</text>
|
|
21
|
+
<line x1="755.0" y1="60" x2="755.0" y2="252" stroke="#eceff1"/>
|
|
22
|
+
<line x1="810.0" y1="60" x2="810.0" y2="252" stroke="#cfd8dc"/>
|
|
23
|
+
<text x="810.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">14:00</text>
|
|
24
|
+
<line x1="865.0" y1="60" x2="865.0" y2="252" stroke="#eceff1"/>
|
|
25
|
+
<line x1="920.0" y1="60" x2="920.0" y2="252" stroke="#cfd8dc"/>
|
|
26
|
+
<text x="920.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">15:00</text>
|
|
27
|
+
<line x1="975.0" y1="60" x2="975.0" y2="252" stroke="#eceff1"/>
|
|
28
|
+
<line x1="1030.0" y1="60" x2="1030.0" y2="252" stroke="#cfd8dc"/>
|
|
29
|
+
<text x="1030.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">16:00</text>
|
|
30
|
+
<line x1="1085.0" y1="60" x2="1085.0" y2="252" stroke="#eceff1"/>
|
|
31
|
+
<line x1="1140.0" y1="60" x2="1140.0" y2="252" stroke="#cfd8dc"/>
|
|
32
|
+
<text x="1140.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">17:00</text>
|
|
33
|
+
<line x1="1195.0" y1="60" x2="1195.0" y2="252" stroke="#eceff1"/>
|
|
34
|
+
<line x1="1250.0" y1="60" x2="1250.0" y2="252" stroke="#cfd8dc"/>
|
|
35
|
+
<text x="1250.0" y="57" font-size="10" text-anchor="middle" fill="#607d8b">18:00</text>
|
|
36
|
+
<text x="20" y="96" font-size="12" fill="#263238">Auditorium</text><line x1="150" y1="124" x2="1250.0" y2="124" stroke="#b0bec5"/>
|
|
37
|
+
<rect x="150.0" y="62" width="1100.0" height="60" fill="#f1f8e9" opacity="0.6"/>
|
|
38
|
+
<rect x="205.0" y="68" width="55.0" height="48" fill="#fff8e1" stroke="#ffb300" stroke-dasharray="3,2"/>
|
|
39
|
+
<rect x="260.0" y="66" width="82.5" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
|
|
40
|
+
<text x="264.0" y="84" font-size="10" fill="#0d47a1">Launch keyno…</text><text x="264.0" y="98" font-size="9" fill="#546e7a">09:00–09:45</text>
|
|
41
|
+
<rect x="379.2" y="66" width="110.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
|
|
42
|
+
<text x="383.2" y="84" font-size="10" fill="#0d47a1">Customer panel</text><text x="383.2" y="98" font-size="9" fill="#546e7a">10:05–11:05</text>
|
|
43
|
+
<rect x="535.0" y="66" width="110.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
|
|
44
|
+
<text x="539.0" y="84" font-size="10" fill="#0d47a1">Lunch</text><text x="539.0" y="98" font-size="9" fill="#546e7a">11:30–12:30</text>
|
|
45
|
+
<rect x="1085.0" y="66" width="55.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
|
|
46
|
+
<text x="1089.0" y="84" font-size="10" fill="#0d47a1">Closing…</text><text x="1089.0" y="98" font-size="9" fill="#546e7a">16:30–17:00</text>
|
|
47
|
+
<text x="20" y="160" font-size="12" fill="#263238">Demo lab</text><line x1="150" y1="188" x2="1250.0" y2="188" stroke="#b0bec5"/>
|
|
48
|
+
<rect x="260.0" y="126" width="880.0" height="60" fill="#f1f8e9" opacity="0.6"/>
|
|
49
|
+
<rect x="351.7" y="130" width="82.5" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
|
|
50
|
+
<text x="355.7" y="148" font-size="10" fill="#0d47a1">Live demo: s…</text><text x="355.7" y="162" font-size="9" fill="#546e7a">09:50–10:35</text>
|
|
51
|
+
<rect x="700.0" y="130" width="275.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
|
|
52
|
+
<text x="704.0" y="148" font-size="10" fill="#0d47a1">Hands-on lab</text><text x="704.0" y="162" font-size="9" fill="#546e7a">13:00–15:30</text>
|
|
53
|
+
<text x="20" y="224" font-size="12" fill="#263238">Foyer</text><line x1="150" y1="252" x2="1250.0" y2="252" stroke="#b0bec5"/>
|
|
54
|
+
<rect x="150.0" y="190" width="1100.0" height="60" fill="#f1f8e9" opacity="0.6"/>
|
|
55
|
+
<rect x="205.0" y="194" width="55.0" height="52" rx="4" fill="#e3f2fd" stroke="#1565c0" stroke-width="1"/>
|
|
56
|
+
<text x="209.0" y="212" font-size="10" fill="#0d47a1">Doors a…</text><text x="209.0" y="226" font-size="9" fill="#546e7a">08:30–09:00</text>
|
|
57
|
+
<text x="20" y="282" font-size="12" fill="#2e7d32">PASS: no clashes</text><text x="20" y="298" font-size="10" fill="#666">Dashed amber: setup before an item · green band: room open</text>
|
|
58
|
+
</svg>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "run-of-show",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Turn an event's agenda into a timed run of show — every session, speaker, changeover and break with its start, end, room and who's needed — and check it with a script for two things in one room at once, a speaker booked in two places, changeovers shorter than the turnaround and anything outside venue hours, drawn as a timeline per room. Use when an organiser asks for \"a run of show\", \"a running order\", \"the minute-by-minute for the day\", \"check the agenda for clashes\", or has sessions, speakers and rooms for a conference, launch, festival or wedding day.",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": {
|
|
7
|
+
"npm": [],
|
|
8
|
+
"pip": []
|
|
9
|
+
},
|
|
10
|
+
"env": {
|
|
11
|
+
"required": [],
|
|
12
|
+
"optional": []
|
|
13
|
+
},
|
|
14
|
+
"related": [
|
|
15
|
+
"seating-plan"
|
|
16
|
+
],
|
|
17
|
+
"handoff": [
|
|
18
|
+
{
|
|
19
|
+
"to": "seating-plan",
|
|
20
|
+
"when": "the day includes a seated meal or dinner",
|
|
21
|
+
"purpose": "Seat the dinner guests so every together, apart and access request holds.",
|
|
22
|
+
"fallback": "If seating-plan is absent, list each table with its guests and check by hand that no table is over its count and every keep-apart pair is split."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"tags": [
|
|
26
|
+
"events",
|
|
27
|
+
"run-of-show",
|
|
28
|
+
"agenda",
|
|
29
|
+
"schedule",
|
|
30
|
+
"conference",
|
|
31
|
+
"timeline",
|
|
32
|
+
"svg"
|
|
33
|
+
],
|
|
34
|
+
"permissions": {
|
|
35
|
+
"network": [],
|
|
36
|
+
"commands": [],
|
|
37
|
+
"files": "named-paths",
|
|
38
|
+
"notes": "Reads the show.json it is given and writes the SVG to the path passed with --svg."
|
|
39
|
+
},
|
|
40
|
+
"model": {
|
|
41
|
+
"tier": "standard",
|
|
42
|
+
"rationale": "Ordering a day around rooms and people is bounded judgment; the script decides whether anything clashes."
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check an event's run of show for clashes and draw it as a timeline. Stdlib only.
|
|
3
|
+
|
|
4
|
+
Reads a show.json: the rooms (opening hours, turnaround between items), the items (title,
|
|
5
|
+
room, start, end or duration, the people needed, setup minutes needed in the room before it
|
|
6
|
+
starts, changeover minutes needed after it), the travel minutes between rooms, and the
|
|
7
|
+
published end of the day. It fails a show where
|
|
8
|
+
- an item ends before it starts
|
|
9
|
+
- two items overlap in one room
|
|
10
|
+
- the gap between two items in a room is shorter than the turnaround, the next item's
|
|
11
|
+
setup or the last item's changeover
|
|
12
|
+
- an item, with its setup, runs outside its room's opening hours
|
|
13
|
+
- a person is needed in two places at once, or has less than the travel time between
|
|
14
|
+
rooms
|
|
15
|
+
and warns where
|
|
16
|
+
- a person works more than --max-hours without a break of --break minutes
|
|
17
|
+
- a room sits empty for more than --max-gap minutes between items
|
|
18
|
+
- the last item ends after the published end
|
|
19
|
+
Then it prints the running order, and with --svg draws one lane per room with each item as
|
|
20
|
+
a block at its times, failures in red.
|
|
21
|
+
|
|
22
|
+
Times are 24-hour "HH:MM". Items on several days carry a "day" (any label, such as
|
|
23
|
+
"2026-06-12" or "Day 2"); each day is checked on its own.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
python3 show_check.py show.json [--svg timeline.svg] [--json]
|
|
27
|
+
[--max-hours 3] [--break 15] [--max-gap 45]
|
|
28
|
+
Exit codes: 0 no failures, 1 a failure, 2 bad input.
|
|
29
|
+
"""
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import math
|
|
33
|
+
import re
|
|
34
|
+
import sys
|
|
35
|
+
|
|
36
|
+
TIME = re.compile(r"^([01]?\d|2[0-4]):([0-5]\d)$")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BadInput(ValueError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def minutes(v, what):
|
|
44
|
+
"""'09:30' -> 570. Raises BadInput."""
|
|
45
|
+
m = TIME.match(str(v).strip()) if v is not None else None
|
|
46
|
+
if not m or (int(m.group(1)) == 24 and m.group(2) != "00"):
|
|
47
|
+
raise BadInput(f"{what} must be a 24-hour time like 09:30, got {v!r}")
|
|
48
|
+
return int(m.group(1)) * 60 + int(m.group(2))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def hhmm(n):
|
|
52
|
+
return f"{int(n) // 60:02d}:{int(n) % 60:02d}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _mins(v, what):
|
|
56
|
+
if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v) or v < 0:
|
|
57
|
+
raise BadInput(f"{what} must be a number of minutes, got {v!r}")
|
|
58
|
+
return float(v)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load(data):
|
|
62
|
+
if not isinstance(data, dict):
|
|
63
|
+
raise BadInput("the file must hold a JSON object")
|
|
64
|
+
default_turn = _mins(data.get("turnaround", 0), "turnaround")
|
|
65
|
+
rooms = {}
|
|
66
|
+
for i, r in enumerate(data.get("rooms") or []):
|
|
67
|
+
if not isinstance(r, dict) or not r.get("name"):
|
|
68
|
+
raise BadInput(f"room {i + 1} needs a name")
|
|
69
|
+
n = str(r["name"])
|
|
70
|
+
rooms[n] = {"name": n, "open": minutes(r["open"], f"room {n} open") if r.get("open") else None,
|
|
71
|
+
"close": minutes(r["close"], f"room {n} close") if r.get("close") else None,
|
|
72
|
+
"turnaround": _mins(r.get("turnaround", default_turn), f"room {n} turnaround")}
|
|
73
|
+
items = []
|
|
74
|
+
for i, it in enumerate(data.get("items") or []):
|
|
75
|
+
if not isinstance(it, dict) or not it.get("title"):
|
|
76
|
+
raise BadInput(f"item {i + 1} needs a title")
|
|
77
|
+
iid = str(it.get("id") or f"item {i + 1}")
|
|
78
|
+
room = str(it.get("room") or "")
|
|
79
|
+
if not room:
|
|
80
|
+
raise BadInput(f"{iid} ({it['title']}) needs a room")
|
|
81
|
+
if room not in rooms:
|
|
82
|
+
rooms[room] = {"name": room, "open": None, "close": None, "turnaround": default_turn}
|
|
83
|
+
start = minutes(it.get("start"), f"{iid} start")
|
|
84
|
+
if it.get("end") is not None:
|
|
85
|
+
end = minutes(it["end"], f"{iid} end")
|
|
86
|
+
elif it.get("duration") is not None:
|
|
87
|
+
end = start + _mins(it["duration"], f"{iid} duration")
|
|
88
|
+
else:
|
|
89
|
+
raise BadInput(f"{iid} ({it['title']}) needs an end or a duration")
|
|
90
|
+
people = it.get("people") or []
|
|
91
|
+
if isinstance(people, str):
|
|
92
|
+
people = [people]
|
|
93
|
+
items.append({"id": iid, "title": str(it["title"]), "room": room, "day": str(it.get("day") or ""),
|
|
94
|
+
"start": start, "end": end, "people": [str(p) for p in people],
|
|
95
|
+
"setup": _mins(it.get("setup", 0), f"{iid} setup"),
|
|
96
|
+
"changeover": _mins(it.get("changeover", 0), f"{iid} changeover")})
|
|
97
|
+
if not items:
|
|
98
|
+
raise BadInput("no items")
|
|
99
|
+
travel = {}
|
|
100
|
+
for i, t in enumerate(data.get("travel") or []):
|
|
101
|
+
if not isinstance(t, dict) or not isinstance(t.get("between"), list) or len(t["between"]) != 2:
|
|
102
|
+
raise BadInput(f"travel {i + 1} must be {{\"between\": [room, room], \"minutes\": n}}")
|
|
103
|
+
a, b = (str(x) for x in t["between"])
|
|
104
|
+
travel[frozenset((a, b))] = _mins(t.get("minutes"), f"travel {i + 1} minutes")
|
|
105
|
+
end = minutes(data["published_end"], "published_end") if data.get("published_end") else None
|
|
106
|
+
return {"rooms": rooms, "items": items, "travel": travel, "published_end": end}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def check(show, max_hours=3.0, brk=15.0, max_gap=45.0):
|
|
110
|
+
fails, warns, bad = [], [], set()
|
|
111
|
+
items = show["items"]
|
|
112
|
+
label = lambda it: f"{it['title']} ({hhmm(it['start'])}{', ' + it['day'] if it['day'] else ''})"
|
|
113
|
+
for it in items:
|
|
114
|
+
if it["end"] <= it["start"]:
|
|
115
|
+
fails.append(f"{label(it)} ends at {hhmm(it['end'])}, not after it starts")
|
|
116
|
+
bad.add(it["id"])
|
|
117
|
+
ok = [it for it in items if it["end"] > it["start"]]
|
|
118
|
+
groups = {}
|
|
119
|
+
for it in ok:
|
|
120
|
+
groups.setdefault((it["day"], it["room"]), []).append(it)
|
|
121
|
+
for (day, room), lane in groups.items():
|
|
122
|
+
r = show["rooms"][room]
|
|
123
|
+
lane.sort(key=lambda it: it["start"])
|
|
124
|
+
for it in lane:
|
|
125
|
+
if r["open"] is not None and it["start"] - it["setup"] < r["open"]:
|
|
126
|
+
need = f" with {it['setup']:.0f} min setup" if it["setup"] else ""
|
|
127
|
+
fails.append(f"{label(it)} starts{need} before {room} opens at {hhmm(r['open'])}")
|
|
128
|
+
bad.add(it["id"])
|
|
129
|
+
if r["close"] is not None and it["end"] > r["close"]:
|
|
130
|
+
fails.append(f"{label(it)} ends at {hhmm(it['end'])}, after {room} closes at {hhmm(r['close'])}")
|
|
131
|
+
bad.add(it["id"])
|
|
132
|
+
for a, b in zip(lane, lane[1:]):
|
|
133
|
+
gap = b["start"] - a["end"]
|
|
134
|
+
if gap < 0:
|
|
135
|
+
fails.append(f"{label(a)} and {label(b)} overlap in {room} by {-gap:.0f} min")
|
|
136
|
+
bad.update((a["id"], b["id"]))
|
|
137
|
+
continue
|
|
138
|
+
need = max(r["turnaround"], b["setup"], a["changeover"])
|
|
139
|
+
if gap < need:
|
|
140
|
+
why = ("the room's turnaround" if need == r["turnaround"] else
|
|
141
|
+
f"{b['title']}'s setup" if need == b["setup"] else f"{a['title']}'s changeover")
|
|
142
|
+
fails.append(f"{room}: {gap:.0f} min between {label(a)} and {label(b)}, "
|
|
143
|
+
f"{need:.0f} min needed for {why}")
|
|
144
|
+
bad.update((a["id"], b["id"]))
|
|
145
|
+
elif gap > max_gap:
|
|
146
|
+
warns.append(f"{room} is empty for {gap:.0f} min between {a['title']} and {b['title']}")
|
|
147
|
+
people = {}
|
|
148
|
+
for it in ok:
|
|
149
|
+
for p in it["people"]:
|
|
150
|
+
people.setdefault((it["day"], p), []).append(it)
|
|
151
|
+
for (day, p), mine in people.items():
|
|
152
|
+
mine.sort(key=lambda it: it["start"])
|
|
153
|
+
for a, b in zip(mine, mine[1:]):
|
|
154
|
+
gap = b["start"] - a["end"]
|
|
155
|
+
if gap < 0:
|
|
156
|
+
fails.append(f"{p} is needed at {label(a)} in {a['room']} and {label(b)} in {b['room']} at once")
|
|
157
|
+
bad.update((a["id"], b["id"]))
|
|
158
|
+
elif a["room"] != b["room"]:
|
|
159
|
+
t = show["travel"].get(frozenset((a["room"], b["room"])))
|
|
160
|
+
if t is not None and gap < t:
|
|
161
|
+
fails.append(f"{p} has {gap:.0f} min to get from {a['room']} to {b['room']} "
|
|
162
|
+
f"between {a['title']} and {b['title']}; the walk takes {t:.0f}")
|
|
163
|
+
bad.update((a["id"], b["id"]))
|
|
164
|
+
run_start, last_end = None, None
|
|
165
|
+
for it in mine:
|
|
166
|
+
if last_end is None or it["start"] - last_end >= brk:
|
|
167
|
+
run_start = it["start"]
|
|
168
|
+
last_end = max(last_end or it["end"], it["end"])
|
|
169
|
+
if last_end - run_start > max_hours * 60:
|
|
170
|
+
warns.append(f"{p} works from {hhmm(run_start)} to {hhmm(last_end)}"
|
|
171
|
+
f"{' on ' + day if day else ''} without a {brk:.0f}-minute break")
|
|
172
|
+
run_start = math.inf # one warning per stretch
|
|
173
|
+
# an inf run_start makes the difference negative until the next break resets it
|
|
174
|
+
if show["published_end"] is not None:
|
|
175
|
+
for day in sorted({it["day"] for it in ok}):
|
|
176
|
+
last = max((it for it in ok if it["day"] == day), key=lambda it: it["end"])
|
|
177
|
+
if last["end"] > show["published_end"]:
|
|
178
|
+
warns.append(f"{last['title']} ends at {hhmm(last['end'])}{', ' + day if day else ''}, "
|
|
179
|
+
f"{last['end'] - show['published_end']:.0f} min after the published end "
|
|
180
|
+
f"of {hhmm(show['published_end'])}")
|
|
181
|
+
return {"fails": fails, "warns": warns, "bad": sorted(bad)}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def order(show):
|
|
185
|
+
return sorted(show["items"], key=lambda it: (it["day"], it["start"], it["room"]))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def report(show, r):
|
|
189
|
+
out, day = [], None
|
|
190
|
+
for it in order(show):
|
|
191
|
+
if it["day"] != day and it["day"]:
|
|
192
|
+
out.append(it["day"])
|
|
193
|
+
day = it["day"]
|
|
194
|
+
who = f" — {', '.join(it['people'])}" if it["people"] else ""
|
|
195
|
+
out.append(f" {hhmm(it['start'])}–{hhmm(it['end'])} {it['room']}: {it['title']}{who}")
|
|
196
|
+
out += [f"FAIL {f}" for f in r["fails"]] + [f"WARN {w}" for w in r["warns"]]
|
|
197
|
+
out.append("PASS" if not r["fails"] else f"{len(r['fails'])} failure(s)")
|
|
198
|
+
return "\n".join(out) + "\n"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def esc(t):
|
|
202
|
+
return str(t).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def svg(show, r, px=3.0, lane=64):
|
|
206
|
+
"""One panel per day: a lane per room, a block per item, a tick every 30 minutes."""
|
|
207
|
+
days = sorted({it["day"] for it in show["items"]})
|
|
208
|
+
rooms = list(show["rooms"])
|
|
209
|
+
out_body, y = [], 50
|
|
210
|
+
width = 0
|
|
211
|
+
for day in days:
|
|
212
|
+
its = [it for it in show["items"] if it["day"] == day]
|
|
213
|
+
t0 = min(min(it["start"] for it in its), *(show["rooms"][it["room"]]["open"] or it["start"] for it in its))
|
|
214
|
+
t1 = max(max(it["end"] for it in its), *(show["rooms"][it["room"]]["close"] or it["end"] for it in its))
|
|
215
|
+
t0, t1 = 30 * math.floor(t0 / 30), 30 * math.ceil(t1 / 30)
|
|
216
|
+
scale = min(px, 1100.0 / max(t1 - t0, 30)) # a long day still fits a page
|
|
217
|
+
X = lambda t, t0=t0, scale=scale: 150 + (t - t0) * scale
|
|
218
|
+
width = max(width, X(t1) + 30)
|
|
219
|
+
if day:
|
|
220
|
+
out_body.append(f'<text x="20" y="{y:.0f}" font-size="14" font-weight="bold">{esc(day)}</text>')
|
|
221
|
+
y += 14
|
|
222
|
+
top = y + 10
|
|
223
|
+
for t in range(int(t0), int(t1) + 1, 30):
|
|
224
|
+
major = t % 60 == 0
|
|
225
|
+
out_body.append(f'<line x1="{X(t):.1f}" y1="{top:.0f}" x2="{X(t):.1f}" y2="{top + lane * len(rooms):.0f}" '
|
|
226
|
+
f'stroke="{"#cfd8dc" if major else "#eceff1"}"/>')
|
|
227
|
+
if major:
|
|
228
|
+
out_body.append(f'<text x="{X(t):.1f}" y="{top - 3:.0f}" font-size="10" text-anchor="middle" fill="#607d8b">{hhmm(t)}</text>')
|
|
229
|
+
for k, room in enumerate(rooms):
|
|
230
|
+
ly = top + k * lane
|
|
231
|
+
rm = show["rooms"][room]
|
|
232
|
+
out_body.append(f'<text x="20" y="{ly + lane / 2 + 4:.0f}" font-size="12" fill="#263238">{esc(room)}</text>'
|
|
233
|
+
f'<line x1="150" y1="{ly + lane:.0f}" x2="{X(t1):.1f}" y2="{ly + lane:.0f}" stroke="#b0bec5"/>')
|
|
234
|
+
if rm["open"] is not None and rm["close"] is not None:
|
|
235
|
+
out_body.append(f'<rect x="{X(rm["open"]):.1f}" y="{ly + 2:.0f}" width="{(rm["close"] - rm["open"]) * scale:.1f}" '
|
|
236
|
+
f'height="{lane - 4}" fill="#f1f8e9" opacity="0.6"/>')
|
|
237
|
+
for it in its:
|
|
238
|
+
if it["room"] != room:
|
|
239
|
+
continue
|
|
240
|
+
bad = it["id"] in r["bad"]
|
|
241
|
+
x0, w = X(it["start"]), max((it["end"] - it["start"]) * scale, 2)
|
|
242
|
+
if it["setup"]:
|
|
243
|
+
out_body.append(f'<rect x="{x0 - it["setup"] * scale:.1f}" y="{ly + 8:.0f}" width="{it["setup"] * scale:.1f}" '
|
|
244
|
+
f'height="{lane - 16}" fill="#fff8e1" stroke="#ffb300" stroke-dasharray="3,2"/>')
|
|
245
|
+
out_body.append(f'<rect x="{x0:.1f}" y="{ly + 6:.0f}" width="{w:.1f}" height="{lane - 12}" rx="4" '
|
|
246
|
+
f'fill="{"#ffebee" if bad else "#e3f2fd"}" stroke="{"#c62828" if bad else "#1565c0"}" '
|
|
247
|
+
f'stroke-width="{2.5 if bad else 1}"/>')
|
|
248
|
+
title = it["title"] if len(it["title"]) * 6 < w - 6 else it["title"][: max(int((w - 10) / 6), 1)] + "…"
|
|
249
|
+
out_body.append(f'<text x="{x0 + 4:.1f}" y="{ly + 24:.0f}" font-size="10" fill="#0d47a1">{esc(title)}</text>'
|
|
250
|
+
f'<text x="{x0 + 4:.1f}" y="{ly + 38:.0f}" font-size="9" fill="#546e7a">'
|
|
251
|
+
f'{hhmm(it["start"])}–{hhmm(it["end"])}{" ✗" if bad else ""}</text>')
|
|
252
|
+
y = top + lane * len(rooms) + 30
|
|
253
|
+
status = "PASS: no clashes" if not r["fails"] else f"{len(r['fails'])} clash(es): items marked ✗ in red"
|
|
254
|
+
out_body.append(f'<text x="20" y="{y:.0f}" font-size="12" fill="{"#2e7d32" if not r["fails"] else "#c62828"}">{esc(status)}</text>'
|
|
255
|
+
f'<text x="20" y="{y + 16:.0f}" font-size="10" fill="#666">Dashed amber: setup before an item · '
|
|
256
|
+
f'green band: room open</text>')
|
|
257
|
+
H, W = y + 34, max(width, 520)
|
|
258
|
+
head = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W:.0f} {H:.0f}" width="{W:.0f}" height="{H:.0f}" '
|
|
259
|
+
'font-family="Helvetica, Arial, sans-serif">', f'<rect width="{W:.0f}" height="{H:.0f}" fill="#ffffff"/>',
|
|
260
|
+
'<text x="20" y="28" font-size="16" font-weight="bold">Run of show</text>']
|
|
261
|
+
return "\n".join(head + out_body + ["</svg>"]) + "\n"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def main(argv=None):
|
|
265
|
+
ap = argparse.ArgumentParser(description="Check a run of show for clashes and draw it as a timeline.")
|
|
266
|
+
ap.add_argument("show")
|
|
267
|
+
ap.add_argument("--svg", help="write the timeline here")
|
|
268
|
+
ap.add_argument("--json", action="store_true", help="print the result as JSON")
|
|
269
|
+
ap.add_argument("--max-hours", type=float, default=3.0, help="hours a person may work without a break (default 3)")
|
|
270
|
+
ap.add_argument("--break", dest="brk", type=float, default=15.0, help="minutes that count as a break (default 15)")
|
|
271
|
+
ap.add_argument("--max-gap", type=float, default=45.0, help="minutes a room may sit empty between items (default 45)")
|
|
272
|
+
args = ap.parse_args(argv)
|
|
273
|
+
try:
|
|
274
|
+
with open(args.show, encoding="utf-8") as fh:
|
|
275
|
+
show = load(json.load(fh))
|
|
276
|
+
except (OSError, ValueError) as e:
|
|
277
|
+
print(f"FAIL cannot read {args.show}: {e}")
|
|
278
|
+
return 2
|
|
279
|
+
r = check(show, args.max_hours, args.brk, args.max_gap)
|
|
280
|
+
if args.json:
|
|
281
|
+
print(json.dumps({"pass": not r["fails"], "fails": r["fails"], "warns": r["warns"],
|
|
282
|
+
"items": [dict(it, start=hhmm(it["start"]), end=hhmm(it["end"])) for it in order(show)]},
|
|
283
|
+
indent=1))
|
|
284
|
+
else:
|
|
285
|
+
print(report(show, r), end="")
|
|
286
|
+
if args.svg:
|
|
287
|
+
with open(args.svg, "w", encoding="utf-8") as fh:
|
|
288
|
+
fh.write(svg(show, r))
|
|
289
|
+
if not args.json:
|
|
290
|
+
print(f"\nWrote {args.svg}")
|
|
291
|
+
return 1 if r["fails"] else 0
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if __name__ == "__main__":
|
|
295
|
+
sys.exit(main())
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"published_end": "17:30",
|
|
3
|
+
"turnaround": 10,
|
|
4
|
+
"rooms": [
|
|
5
|
+
{"name": "Main hall", "open": "08:00", "close": "18:00", "turnaround": 15},
|
|
6
|
+
{"name": "Studio B", "open": "09:00", "close": "17:30"}
|
|
7
|
+
],
|
|
8
|
+
"travel": [{"between": ["Main hall", "Studio B"], "minutes": 5}],
|
|
9
|
+
"items": [
|
|
10
|
+
{"id": "doors", "title": "Doors and registration", "room": "Main hall", "start": "08:30", "end": "09:00", "people": ["Front of house"]},
|
|
11
|
+
{"id": "key", "title": "Opening keynote", "room": "Main hall", "start": "09:15", "duration": 45, "people": ["Ana Ruiz", "AV crew"], "setup": 15},
|
|
12
|
+
{"id": "ws1", "title": "Workshop: pricing", "room": "Studio B", "start": "10:10", "end": "11:00", "people": ["Ana Ruiz"]},
|
|
13
|
+
{"id": "panel", "title": "Customer panel", "room": "Main hall", "start": "10:15", "end": "11:15", "people": ["Joe Park", "AV crew"], "changeover": 20}
|
|
14
|
+
]
|
|
15
|
+
}
|