skilldrop-cli 1.5.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 +4 -1
- package/catalogue.json +25 -2
- package/model-routing.json +20 -0
- package/package.json +1 -1
- package/packs/civil-architect/pack.json +60 -0
- package/packs/civil-architect/skills/floor-plan-layout/SKILL.md +103 -0
- package/packs/civil-architect/skills/floor-plan-layout/evals/eval_queries.json +30 -0
- package/packs/civil-architect/skills/floor-plan-layout/evals/evals.json +57 -0
- package/packs/civil-architect/skills/floor-plan-layout/evals/files/4/adu-programme.md +12 -0
- package/packs/civil-architect/skills/floor-plan-layout/examples/ground-floor.md +65 -0
- package/packs/civil-architect/skills/floor-plan-layout/examples/ground-floor.svg +135 -0
- package/packs/civil-architect/skills/floor-plan-layout/manifest.json +34 -0
- package/packs/civil-architect/skills/floor-plan-layout/scripts/plan_check.py +449 -0
- package/packs/civil-architect/skills/floor-plan-layout/templates/plan.json +38 -0
- 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,84 @@
|
|
|
1
|
+
Name,Party,RSVP,Access needs
|
|
2
|
+
Maureen Ashdown,Maureen & Clive,Yes,
|
|
3
|
+
Clive Ashdown,Maureen & Clive,Yes,
|
|
4
|
+
Graham Petherick,,Yes,
|
|
5
|
+
Lorraine Petherick,,Yes,
|
|
6
|
+
Tom Ashdown,Tom & Leanne,Yes,
|
|
7
|
+
Becky Price,Becky & Gareth,Yes,
|
|
8
|
+
Leanne Ashdown,Tom & Leanne,Yes,
|
|
9
|
+
Gareth Price,Becky & Gareth,Yes,
|
|
10
|
+
Joan Fairbrook,,Yes,Wheelchair
|
|
11
|
+
Priyanka Yelverton,Priyanka & Yara,Yes,
|
|
12
|
+
Yara Okwuosa,Priyanka & Yara,Yes,
|
|
13
|
+
Amaya Larkspur,Amaya & Oren,Yes,
|
|
14
|
+
Oren Thistlewood,Amaya & Oren,Yes,
|
|
15
|
+
Tobias Dimitriou,Tobias & Wendell,Yes,
|
|
16
|
+
Wendell Thistlewood,Tobias & Wendell,Yes,
|
|
17
|
+
Isolde Kowalczyk,Isolde & Sunniva,Yes,Wheelchair
|
|
18
|
+
Sunniva Bellweather,Isolde & Sunniva,Yes,
|
|
19
|
+
Emeka Haugen,Emeka & Cosima,Yes,
|
|
20
|
+
Cosima Rendall,Emeka & Cosima,Yes,
|
|
21
|
+
Tova Quintero,Tova & Oren,Yes,
|
|
22
|
+
Oren Ravensworth,Tova & Oren,Yes,
|
|
23
|
+
Gideon Vexley,Gideon & Isolde,Yes,
|
|
24
|
+
Isolde Rendall,Gideon & Isolde,Yes,
|
|
25
|
+
Lysander Pilbeam,Lysander & Xanthe,Yes,
|
|
26
|
+
Xanthe Dunmore,Lysander & Xanthe,Yes,
|
|
27
|
+
Peregrine Okwuosa,Peregrine & Zubair,Yes,
|
|
28
|
+
Zubair Dimitriou,Peregrine & Zubair,Yes,
|
|
29
|
+
Marguerite Haugen,Marguerite & Thaddeus,Yes,
|
|
30
|
+
Thaddeus Gallagher,Marguerite & Thaddeus,Yes,
|
|
31
|
+
Ginevra Pilbeam,Ginevra & Zelda,Yes,
|
|
32
|
+
Zelda Kilbride,Ginevra & Zelda,Yes,
|
|
33
|
+
Ottoline Calloway,Ottoline & Keziah,Yes,
|
|
34
|
+
Keziah Iwu,Ottoline & Keziah,Yes,
|
|
35
|
+
Valentin Bellweather,Valentin & Wendell,Yes,
|
|
36
|
+
Wendell Nakashima,Valentin & Wendell,Yes,
|
|
37
|
+
Isolde Fairbairn,Isolde & Bram,Yes,
|
|
38
|
+
Bram Inchbald,Isolde & Bram,Yes,
|
|
39
|
+
Zubair Pendragon,Zubair & Delphine,Yes,
|
|
40
|
+
Delphine Vexley,Zubair & Delphine,Yes,
|
|
41
|
+
Zubair Bhattacharya,Zubair & Emeka,Yes,
|
|
42
|
+
Emeka Dimitriou,Zubair & Emeka,Yes,
|
|
43
|
+
Bram Greythorn,Bram & Ottoline,Yes,
|
|
44
|
+
Ottoline Inchbald,Bram & Ottoline,Yes,
|
|
45
|
+
Lysander Thistlewood,Lysander & Gideon,Yes,
|
|
46
|
+
Gideon Glenister,Lysander & Gideon,Yes,
|
|
47
|
+
Anouk Copperfield,Anouk & Hana,Yes,
|
|
48
|
+
Hana Stroud,Anouk & Hana,Yes,
|
|
49
|
+
Keziah Kowalczyk,Keziah & Xanthe,Yes,
|
|
50
|
+
Xanthe Greythorn,Keziah & Xanthe,Yes,
|
|
51
|
+
Barnaby Calloway,Barnaby & Ursula,Yes,
|
|
52
|
+
Ursula Stroud,Barnaby & Ursula,Yes,
|
|
53
|
+
Oren Glenister,Oren & Jethro,Yes,
|
|
54
|
+
Jethro Pilbeam,Oren & Jethro,Yes,
|
|
55
|
+
Ilse Kowalczyk,Kowalczyk family,Yes,
|
|
56
|
+
Vashti Kowalczyk,Kowalczyk family,Yes,
|
|
57
|
+
Rufaro Kowalczyk,Kowalczyk family,Yes,
|
|
58
|
+
Delphine Kowalczyk,Kowalczyk family,Yes,
|
|
59
|
+
Noor Eastwick,Eastwick family,Yes,
|
|
60
|
+
Lysander Eastwick,Eastwick family,Yes,
|
|
61
|
+
Elowen Eastwick,Eastwick family,Yes,
|
|
62
|
+
Gideon Eastwick,Eastwick family,Yes,
|
|
63
|
+
Yusuf Elstree,Elstree family,Yes,
|
|
64
|
+
Anouk Elstree,Elstree family,Yes,
|
|
65
|
+
Thaddeus Elstree,Elstree family,Yes,
|
|
66
|
+
Fintan Elstree,Elstree family,Yes,
|
|
67
|
+
Fintan Rendall,,Yes,Wheelchair
|
|
68
|
+
Lysander Dunmore,,Yes,
|
|
69
|
+
Noor Underhill,,Yes,
|
|
70
|
+
Benedikt Ormsby,,Yes,
|
|
71
|
+
Marguerite Drummond,,Yes,
|
|
72
|
+
Hamish Moreau,,Yes,
|
|
73
|
+
Dashiell Gallagher,,Yes,
|
|
74
|
+
Chidi Mossop,,Yes,
|
|
75
|
+
Vashti Copperfield,,Yes,
|
|
76
|
+
Mireille Nakashima,,Yes,
|
|
77
|
+
Kalinda Gallagher,,Yes,
|
|
78
|
+
Soren Hartigan,,Yes,
|
|
79
|
+
Sunniva Jardine,,Yes,
|
|
80
|
+
Hana Kowalczyk,,Yes,
|
|
81
|
+
Rosamund Gallagher,,Yes,
|
|
82
|
+
Leopold Jardine,,Yes,
|
|
83
|
+
Chidi Kestrel,,Yes,
|
|
84
|
+
Nnamdi Jankowski,,Yes,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Maureen's do - The Orangery, Larkmoor Court Hotel - Sat 23 Jan 2027, 7pm
|
|
2
|
+
|
|
3
|
+
Room: top table (Table 1) is a long table against the window, seats 8.
|
|
4
|
+
Tables 2-9 are rounds of 10. Tables 8 and 9 are out on the raised terrace bit -
|
|
5
|
+
three steps up, no ramp, so no wheelchairs out there.
|
|
6
|
+
|
|
7
|
+
Couples and families sit together (see Party column on the guest list).
|
|
8
|
+
|
|
9
|
+
KEEP APART:
|
|
10
|
+
- Graham Petherick and Lorraine Petherick (divorce still going through - do NOT seat together)
|
|
11
|
+
- Noor Underhill and Lysander Dunmore (fell out badly at work, both still coming)
|
|
12
|
+
- Tova Quintero and Thaddeus Gallagher
|
|
13
|
+
- Ilse Kowalczyk and Marguerite Drummond
|
|
14
|
+
|
|
15
|
+
Maureen said she wants on the top table with her: Clive, her mum Joan, Tom and Leanne,
|
|
16
|
+
and "both Graham and Lorraine - they were my first team and I want them both up there with me".
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Table,Seat,Guest
|
|
2
|
+
1,1,Joan Fairbrook
|
|
3
|
+
1,2,Tom Ashdown
|
|
4
|
+
1,3,Leanne Ashdown
|
|
5
|
+
1,4,Maureen Ashdown
|
|
6
|
+
1,5,Clive Ashdown
|
|
7
|
+
1,6,Graham Petherick
|
|
8
|
+
1,7,Lorraine Petherick
|
|
9
|
+
2,1,Zubair Pendragon
|
|
10
|
+
2,2,Delphine Vexley
|
|
11
|
+
2,3,Anouk Copperfield
|
|
12
|
+
2,4,Hana Stroud
|
|
13
|
+
2,5,Amaya Larkspur
|
|
14
|
+
2,6,Oren Thistlewood
|
|
15
|
+
2,7,Chidi Mossop
|
|
16
|
+
2,8,Hana Kowalczyk
|
|
17
|
+
2,9,Leopold Jardine
|
|
18
|
+
2,10,Soren Hartigan
|
|
19
|
+
2,11,Marguerite Drummond
|
|
20
|
+
3,1,Isolde Kowalczyk
|
|
21
|
+
3,2,Sunniva Bellweather
|
|
22
|
+
3,3,Priyanka Yelverton
|
|
23
|
+
3,4,Yara Okwuosa
|
|
24
|
+
3,5,Tobias Dimitriou
|
|
25
|
+
3,6,Wendell Thistlewood
|
|
26
|
+
3,7,Sunniva Jardine
|
|
27
|
+
3,8,Vashti Copperfield
|
|
28
|
+
3,9,Benedikt Ormsby
|
|
29
|
+
4,1,Noor Eastwick
|
|
30
|
+
4,2,Lysander Eastwick
|
|
31
|
+
4,3,Elowen Eastwick
|
|
32
|
+
4,4,Gideon Eastwick
|
|
33
|
+
4,5,Marguerite Haugen
|
|
34
|
+
4,6,Thaddeus Gallagher
|
|
35
|
+
4,7,Valentin Bellweather
|
|
36
|
+
4,8,Wendell Nakashima
|
|
37
|
+
4,9,Hamish Moreau
|
|
38
|
+
5,1,Barnaby Calloway
|
|
39
|
+
5,2,Ursula Stroud
|
|
40
|
+
5,3,Lysander Thistlewood
|
|
41
|
+
5,4,Gideon Glenister
|
|
42
|
+
5,5,Becky Price
|
|
43
|
+
5,6,Gareth Price
|
|
44
|
+
5,7,Tova Quintero
|
|
45
|
+
5,8,Oren Ravensworth
|
|
46
|
+
5,9,Mireille Nakashima
|
|
47
|
+
5,10,Rosamund Gallagher
|
|
48
|
+
6,1,Yusuf Elstree
|
|
49
|
+
6,2,Anouk Elstree
|
|
50
|
+
6,3,Thaddeus Elstree
|
|
51
|
+
6,4,Fintan Elstree
|
|
52
|
+
6,5,Gideon Vexley
|
|
53
|
+
6,6,Isolde Rendall
|
|
54
|
+
6,7,Ottoline Calloway
|
|
55
|
+
6,8,Keziah Iwu
|
|
56
|
+
6,9,Hamish Moreau
|
|
57
|
+
7,1,Lysander Pilbeam
|
|
58
|
+
7,2,Xanthe Dunmore
|
|
59
|
+
7,3,Ginevra Pilbeam
|
|
60
|
+
7,4,Zelda Kilbride
|
|
61
|
+
7,5,Keziah Kowalczyk
|
|
62
|
+
7,6,Xanthe Greythorn
|
|
63
|
+
7,7,Nnamdi Jankowski
|
|
64
|
+
7,8,Kalinda Gallagher
|
|
65
|
+
7,9,Dashiell Gallagher
|
|
66
|
+
7,10,Chidi Kestrel
|
|
67
|
+
8,1,Oren Glenister
|
|
68
|
+
8,2,Jethro Pilbeam
|
|
69
|
+
8,3,Zubair Bhattacharya
|
|
70
|
+
8,4,Emeka Dimitriou
|
|
71
|
+
8,5,Emeka Haugen
|
|
72
|
+
8,6,Cosima Rendall
|
|
73
|
+
8,7,Isolde Fairbairn
|
|
74
|
+
8,8,Bram Inchbald
|
|
75
|
+
8,9,Lysander Dunmore
|
|
76
|
+
8,10,Noor Underhill
|
|
77
|
+
9,1,Fintan Rendall
|
|
78
|
+
9,2,Ilse Kowalczyk
|
|
79
|
+
9,3,Vashti Kowalczyk
|
|
80
|
+
9,4,Rufaro Kowalczyk
|
|
81
|
+
9,5,Delphine Kowalczyk
|
|
82
|
+
9,6,Bram Greythorn
|
|
83
|
+
9,7,Ottoline Inchbald
|
|
84
|
+
9,8,Peregrine Okwuosa
|
|
85
|
+
9,9,Zubair Dimitriou
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Worked example: a 24-guest wedding dinner
|
|
2
|
+
|
|
3
|
+
Input → output oracle for `seating-plan`. Shows a first draft that broke three requests, a
|
|
4
|
+
conflict in the brief itself (the parents' fixed seats against "keep each household
|
|
5
|
+
together"), and how the reply names it instead of hiding it.
|
|
6
|
+
|
|
7
|
+
## Input
|
|
8
|
+
|
|
9
|
+
> We're seating our wedding dinner: a head table for four and two round tables of ten, so 24
|
|
10
|
+
> seats for 24 guests. The head table is us (Asha and Leo) plus Asha's mum Meera and Leo's dad
|
|
11
|
+
> Ian. Please keep each household together. Asha's grandmother Nani uses a wheelchair, and only
|
|
12
|
+
> the head table and table 1 are step-free. Priya and Dan must not be at the same table.
|
|
13
|
+
> Dietary: Nani, Omar and Ravi are vegetarian; Tess has a nut allergy. Guest list attached.
|
|
14
|
+
|
|
15
|
+
## The first draft, checked
|
|
16
|
+
|
|
17
|
+
Seating households in list order, with the parents fixed at the head table, gave:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
FAIL must sit together but are split: Meera Verma (Head), Raj Verma (T1), Nani Verma (T1)
|
|
21
|
+
FAIL must sit together but are split: Ian Grant (Head), Sue Grant (T1), Kit Grant (T1)
|
|
22
|
+
FAIL Priya Nair and Dan Moore must be kept apart but both sit at table T1
|
|
23
|
+
3 failure(s)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Two of the three can't be fixed by moving people. Meera and Ian are fixed at the head table,
|
|
27
|
+
which seats four, and their households are three each. Keeping both households whole means
|
|
28
|
+
leaving the parents off the head table. That is the couple's choice, so the reply asks; it
|
|
29
|
+
doesn't decide. The third was a straight mistake: Dan moves to table 2 and Jon Park takes his
|
|
30
|
+
seat.
|
|
31
|
+
|
|
32
|
+
## The reply
|
|
33
|
+
|
|
34
|
+
**Every request is met except one that can't be, below.**
|
|
35
|
+
|
|
36
|
+
| Table | Guests |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Head (step-free) | Asha Verma, Leo Grant, Meera Verma, Ian Grant |
|
|
39
|
+
| Table 1 (step-free) | Raj Verma, Nani Verma, Sue Grant, Kit Grant, Omar Diaz, Lia Diaz, Ben Cole, Ada Cole, Priya Nair, Jon Park |
|
|
40
|
+
| Table 2 | Tess Wu, Sam Wu, Nora Bell, Hal Bell, Zoe Lim, Max Lim, Eve Lim, Ravi Shah, Uma Shah, Dan Moore |
|
|
41
|
+
|
|
42
|
+
**One request can't hold as written.** With Meera and Ian at the head table, the Verma and
|
|
43
|
+
Grant households can't each sit together: the head table seats four. Raj and Nani, and Sue and
|
|
44
|
+
Kit, are together at table 1, the table nearest you. If you'd rather keep each household whole,
|
|
45
|
+
the head table becomes just the two of you, and Meera and Ian join their families at table 1.
|
|
46
|
+
|
|
47
|
+
**Caterer's list:** table 1, 2 vegetarian (Nani, Omar); table 2, 1 vegetarian (Ravi) and 1 nut
|
|
48
|
+
allergy (Tess).
|
|
49
|
+
|
|
50
|
+
Nani is at table 1, which is step-free. Priya and Dan are at different tables. The room plan
|
|
51
|
+
with names at each table is in `seating-plan.svg`.
|
|
52
|
+
|
|
53
|
+
## Why this is the oracle
|
|
54
|
+
|
|
55
|
+
- **Every rule is checked before the plan is shown,** and the draft's three failures never
|
|
56
|
+
reach the couple.
|
|
57
|
+
- **A conflict in the brief is named, with the choice it needs.** A plain pass seats the parents
|
|
58
|
+
at the head table and calls the households "together", or quietly drops the head-table
|
|
59
|
+
request.
|
|
60
|
+
- **The reply is for the couple:** names at tables, the caterer's list, and the one decision
|
|
61
|
+
that's theirs. No file formats, no tooling.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 510" width="640" height="510" font-family="Helvetica, Arial, sans-serif">
|
|
2
|
+
<rect width="640" height="510" fill="#ffffff"/>
|
|
3
|
+
<text x="40" y="30" font-size="16" font-weight="bold">Seating plan: 24 of 24 guests, 3 tables</text>
|
|
4
|
+
<rect x="40" y="60" width="560" height="400" fill="#fafafa" stroke="#444" stroke-width="2"/>
|
|
5
|
+
<rect x="26" y="374" width="28" height="12" fill="#2e7d32"/><text x="40" y="402" font-size="10" text-anchor="middle" fill="#2e7d32">EXIT</text>
|
|
6
|
+
<rect x="586" y="374" width="28" height="12" fill="#2e7d32"/><text x="600" y="402" font-size="10" text-anchor="middle" fill="#2e7d32">EXIT</text>
|
|
7
|
+
<rect x="275" y="105" width="91" height="39" fill="#eceff1" stroke="#455a64" stroke-width="1.5"/>
|
|
8
|
+
<text x="320" y="122" font-size="13" font-weight="bold" text-anchor="middle" fill="#455a64">Head</text><text x="320" y="137" font-size="10" text-anchor="middle" fill="#555">4/4 · accessible</text>
|
|
9
|
+
<text x="320" y="98" font-size="9" text-anchor="middle" fill="#222">Asha Verma</text>
|
|
10
|
+
<text x="375" y="127" font-size="9" text-anchor="start" fill="#222">Leo Grant</text>
|
|
11
|
+
<text x="320" y="156" font-size="9" text-anchor="middle" fill="#222">Meera Verma</text>
|
|
12
|
+
<text x="265" y="127" font-size="9" text-anchor="end" fill="#222">Ian Grant</text>
|
|
13
|
+
<circle cx="180" cy="300" r="54" fill="#eceff1" stroke="#455a64" stroke-width="1.5"/>
|
|
14
|
+
<text x="180" y="298" font-size="13" font-weight="bold" text-anchor="middle" fill="#455a64">T1</text><text x="180" y="313" font-size="10" text-anchor="middle" fill="#555">10/10 · accessible</text>
|
|
15
|
+
<text x="180" y="241" font-size="9" text-anchor="middle" fill="#222">Raj Verma</text>
|
|
16
|
+
<text x="216" y="253" font-size="9" text-anchor="start" fill="#222">Nani Verma</text>
|
|
17
|
+
<text x="239" y="284" font-size="9" text-anchor="start" fill="#222">Sue Grant</text>
|
|
18
|
+
<text x="239" y="322" font-size="9" text-anchor="start" fill="#222">Kit Grant</text>
|
|
19
|
+
<text x="216" y="353" font-size="9" text-anchor="start" fill="#222">Omar Diaz</text>
|
|
20
|
+
<text x="180" y="365" font-size="9" text-anchor="middle" fill="#222">Lia Diaz</text>
|
|
21
|
+
<text x="144" y="353" font-size="9" text-anchor="end" fill="#222">Ben Cole</text>
|
|
22
|
+
<text x="121" y="322" font-size="9" text-anchor="end" fill="#222">Ada Cole</text>
|
|
23
|
+
<text x="121" y="284" font-size="9" text-anchor="end" fill="#222">Priya Nair</text>
|
|
24
|
+
<text x="144" y="253" font-size="9" text-anchor="end" fill="#222">Jon Park</text>
|
|
25
|
+
<circle cx="460" cy="300" r="54" fill="#eceff1" stroke="#455a64" stroke-width="1.5"/>
|
|
26
|
+
<text x="460" y="298" font-size="13" font-weight="bold" text-anchor="middle" fill="#455a64">T2</text><text x="460" y="313" font-size="10" text-anchor="middle" fill="#555">10/10</text>
|
|
27
|
+
<text x="460" y="241" font-size="9" text-anchor="middle" fill="#222">Tess Wu</text>
|
|
28
|
+
<text x="496" y="253" font-size="9" text-anchor="start" fill="#222">Sam Wu</text>
|
|
29
|
+
<text x="519" y="284" font-size="9" text-anchor="start" fill="#222">Nora Bell</text>
|
|
30
|
+
<text x="519" y="322" font-size="9" text-anchor="start" fill="#222">Hal Bell</text>
|
|
31
|
+
<text x="496" y="353" font-size="9" text-anchor="start" fill="#222">Zoe Lim</text>
|
|
32
|
+
<text x="460" y="365" font-size="9" text-anchor="middle" fill="#222">Max Lim</text>
|
|
33
|
+
<text x="424" y="353" font-size="9" text-anchor="end" fill="#222">Eve Lim</text>
|
|
34
|
+
<text x="401" y="322" font-size="9" text-anchor="end" fill="#222">Ravi Shah</text>
|
|
35
|
+
<text x="401" y="284" font-size="9" text-anchor="end" fill="#222">Uma Shah</text>
|
|
36
|
+
<text x="424" y="253" font-size="9" text-anchor="end" fill="#222">Dan Moore</text>
|
|
37
|
+
<text x="40" y="488" font-size="12" fill="#2e7d32">PASS: every rule met</text>
|
|
38
|
+
<text x="40" y="506" font-size="10" fill="#666">Positions in metres, 1 m = 40 px · names run clockwise from the top of each table</text>
|
|
39
|
+
</svg>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "seating-plan",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Seat guests at tables for a wedding, gala, conference dinner or awards night, and check the chart with a script — every guest seated once, no table over capacity, people who must sit together together, people who must be kept apart apart, wheelchair users at accessible places — drawn as a room plan with names at each table. Use when an organiser asks to \"make the seating plan\", \"who sits where\", \"seat these 120 guests\", \"check my table plan\", or has a guest list with groups and keep-apart requests.",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": {
|
|
7
|
+
"npm": [],
|
|
8
|
+
"pip": []
|
|
9
|
+
},
|
|
10
|
+
"env": {
|
|
11
|
+
"required": [],
|
|
12
|
+
"optional": []
|
|
13
|
+
},
|
|
14
|
+
"related": [
|
|
15
|
+
"run-of-show"
|
|
16
|
+
],
|
|
17
|
+
"handoff": [
|
|
18
|
+
{
|
|
19
|
+
"to": "run-of-show",
|
|
20
|
+
"when": "the seating is settled and the day's timings are next",
|
|
21
|
+
"purpose": "Time the day around the dinner: doors, speeches, courses and the changeovers between them.",
|
|
22
|
+
"fallback": "If run-of-show is absent, list the day's items with a start, end and room each, and check by hand that no room or person is booked twice."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"tags": [
|
|
26
|
+
"events",
|
|
27
|
+
"seating",
|
|
28
|
+
"wedding",
|
|
29
|
+
"gala",
|
|
30
|
+
"table-plan",
|
|
31
|
+
"svg",
|
|
32
|
+
"constraints"
|
|
33
|
+
],
|
|
34
|
+
"permissions": {
|
|
35
|
+
"network": [],
|
|
36
|
+
"commands": [],
|
|
37
|
+
"files": "named-paths",
|
|
38
|
+
"notes": "Reads the seating.json it is given and writes the SVG to the path passed with --svg."
|
|
39
|
+
},
|
|
40
|
+
"model": {
|
|
41
|
+
"tier": "standard",
|
|
42
|
+
"rationale": "Assigning guests against overlapping rules is bounded judgment; the script decides whether every rule holds."
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check a table seating plan against its rules and draw the room. Stdlib only.
|
|
3
|
+
|
|
4
|
+
Reads a seating.json: the tables (capacity, shape, position in metres, accessible or not),
|
|
5
|
+
the guests (name, party or household, needs), the rules (groups that sit together, pairs
|
|
6
|
+
kept apart, fixed seats) and the assignments (table -> guests). It fails a plan where
|
|
7
|
+
- a guest is unseated, or seated twice
|
|
8
|
+
- a table holds more guests than its capacity
|
|
9
|
+
- a together group is split across tables
|
|
10
|
+
- an apart pair shares a table
|
|
11
|
+
- a wheelchair user sits at a table not marked accessible
|
|
12
|
+
- a guest with a fixed seat is not at that table
|
|
13
|
+
and warns where
|
|
14
|
+
- a household or party that would fit at one table is split
|
|
15
|
+
- a table is under half full, or empty
|
|
16
|
+
- a guest who came alone sits at a table where everyone else is one other party
|
|
17
|
+
- a guest who needs to be near an exit is further than --exit-distance from one (when
|
|
18
|
+
the room's exits are given)
|
|
19
|
+
Then it lists every table with its guests and dietary needs, and with --svg draws the room:
|
|
20
|
+
tables at their positions, labelled with their fill, names around each, failures in red.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
python3 seating_check.py seating.json [--svg room.svg] [--json] [--exit-distance 8]
|
|
24
|
+
Exit codes: 0 no failures, 1 a failure, 2 bad input.
|
|
25
|
+
"""
|
|
26
|
+
import argparse
|
|
27
|
+
import json
|
|
28
|
+
import math
|
|
29
|
+
import sys
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BadInput(ValueError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _num(v, what):
|
|
37
|
+
if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v):
|
|
38
|
+
raise BadInput(f"{what} must be a number, got {v!r}")
|
|
39
|
+
return float(v)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _point(v, what):
|
|
43
|
+
if not isinstance(v, (list, tuple)) or len(v) != 2:
|
|
44
|
+
raise BadInput(f"{what} must be [x, y] in metres")
|
|
45
|
+
return (_num(v[0], what + " x"), _num(v[1], what + " y"))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load(data):
|
|
49
|
+
"""Validate the raw JSON and return a normalised plan. Raises BadInput."""
|
|
50
|
+
if not isinstance(data, dict):
|
|
51
|
+
raise BadInput("the file must hold a JSON object")
|
|
52
|
+
tables, guests = {}, {}
|
|
53
|
+
for i, t in enumerate(data.get("tables") or []):
|
|
54
|
+
if not isinstance(t, dict) or not t.get("id"):
|
|
55
|
+
raise BadInput(f"table {i + 1} needs an id")
|
|
56
|
+
tid = str(t["id"])
|
|
57
|
+
if tid in tables:
|
|
58
|
+
raise BadInput(f"two tables are called {tid!r}")
|
|
59
|
+
cap = _num(t.get("capacity", 10), f"table {tid} capacity")
|
|
60
|
+
if cap < 1 or cap != int(cap):
|
|
61
|
+
raise BadInput(f"table {tid} capacity must be a whole number of seats")
|
|
62
|
+
shape = str(t.get("shape", "round"))
|
|
63
|
+
if shape not in ("round", "rect"):
|
|
64
|
+
raise BadInput(f"table {tid} shape must be round or rect")
|
|
65
|
+
tables[tid] = {"id": tid, "capacity": int(cap), "shape": shape,
|
|
66
|
+
"at": _point(t["at"], f"table {tid} position") if "at" in t else None,
|
|
67
|
+
"accessible": bool(t.get("accessible", False)), "label": str(t.get("label", ""))}
|
|
68
|
+
if not tables:
|
|
69
|
+
raise BadInput("no tables")
|
|
70
|
+
for i, g in enumerate(data.get("guests") or []):
|
|
71
|
+
if not isinstance(g, dict) or not g.get("id"):
|
|
72
|
+
raise BadInput(f"guest {i + 1} needs an id")
|
|
73
|
+
gid = str(g["id"])
|
|
74
|
+
if gid in guests:
|
|
75
|
+
raise BadInput(f"two guests have the id {gid!r}")
|
|
76
|
+
needs = g.get("needs") or []
|
|
77
|
+
if isinstance(needs, str):
|
|
78
|
+
needs = [needs]
|
|
79
|
+
guests[gid] = {"id": gid, "name": str(g.get("name") or gid), "party": str(g.get("party") or ""),
|
|
80
|
+
"needs": [str(n).lower() for n in needs], "dietary": str(g.get("dietary") or "")}
|
|
81
|
+
if not guests:
|
|
82
|
+
raise BadInput("no guests")
|
|
83
|
+
|
|
84
|
+
def known(gid, where):
|
|
85
|
+
if str(gid) not in guests:
|
|
86
|
+
raise BadInput(f"{where} names a guest {gid!r} who is not on the guest list")
|
|
87
|
+
return str(gid)
|
|
88
|
+
|
|
89
|
+
together = []
|
|
90
|
+
for i, grp in enumerate(data.get("together") or []):
|
|
91
|
+
if not isinstance(grp, list) or len(grp) < 2:
|
|
92
|
+
raise BadInput(f"together group {i + 1} must list two or more guests")
|
|
93
|
+
together.append([known(x, f"together group {i + 1}") for x in grp])
|
|
94
|
+
apart = []
|
|
95
|
+
for i, pair in enumerate(data.get("apart") or []):
|
|
96
|
+
if not isinstance(pair, list) or len(pair) != 2:
|
|
97
|
+
raise BadInput(f"apart pair {i + 1} must list exactly two guests")
|
|
98
|
+
apart.append([known(x, f"apart pair {i + 1}") for x in pair])
|
|
99
|
+
fixed = {}
|
|
100
|
+
if not isinstance(data.get("fixed") or {}, dict):
|
|
101
|
+
raise BadInput("fixed must map a guest id to a table id")
|
|
102
|
+
for gid, tid in (data.get("fixed") or {}).items():
|
|
103
|
+
if str(tid) not in tables:
|
|
104
|
+
raise BadInput(f"the fixed seat for {gid} names a table {tid!r} that does not exist")
|
|
105
|
+
fixed[known(gid, "fixed")] = str(tid)
|
|
106
|
+
|
|
107
|
+
raw = data.get("assignments") or {}
|
|
108
|
+
if not isinstance(raw, dict):
|
|
109
|
+
raise BadInput("assignments must map each table id to a list of guest ids")
|
|
110
|
+
seats = {tid: [] for tid in tables}
|
|
111
|
+
for tid, gl in raw.items():
|
|
112
|
+
if str(tid) not in tables:
|
|
113
|
+
raise BadInput(f"assignments name a table {tid!r} that does not exist")
|
|
114
|
+
if not isinstance(gl, list):
|
|
115
|
+
raise BadInput(f"assignments for table {tid} must be a list of guest ids")
|
|
116
|
+
seats[str(tid)] += [known(g, f"table {tid}") for g in gl]
|
|
117
|
+
exits = [_point(e, f"exit {i + 1}") for i, e in enumerate(data.get("exits") or [])]
|
|
118
|
+
room = data.get("room") or {}
|
|
119
|
+
if not isinstance(room, dict):
|
|
120
|
+
raise BadInput("room must be {\"width\": metres, \"depth\": metres}")
|
|
121
|
+
return {"tables": tables, "guests": guests, "together": together, "apart": apart, "fixed": fixed,
|
|
122
|
+
"seats": seats, "exits": exits,
|
|
123
|
+
"room": (_num(room["width"], "room width"), _num(room["depth"], "room depth"))
|
|
124
|
+
if room.get("width") and room.get("depth") else None}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def check(plan, exit_distance=8.0):
|
|
128
|
+
tables, guests, seats = plan["tables"], plan["guests"], plan["seats"]
|
|
129
|
+
fails, warns, bad_tables = [], [], set()
|
|
130
|
+
where = {}
|
|
131
|
+
for tid, gl in seats.items():
|
|
132
|
+
for g in gl:
|
|
133
|
+
where.setdefault(g, []).append(tid)
|
|
134
|
+
name = lambda g: guests[g]["name"]
|
|
135
|
+
for g in guests:
|
|
136
|
+
at = where.get(g, [])
|
|
137
|
+
if not at:
|
|
138
|
+
fails.append(f"{name(g)} has no seat")
|
|
139
|
+
elif len(at) > 1:
|
|
140
|
+
fails.append(f"{name(g)} is seated more than once ({', '.join(at)})")
|
|
141
|
+
bad_tables.update(at)
|
|
142
|
+
table_of = {g: at[0] for g, at in where.items() if len(at) == 1}
|
|
143
|
+
for tid, gl in seats.items():
|
|
144
|
+
t = tables[tid]
|
|
145
|
+
if len(gl) > t["capacity"]:
|
|
146
|
+
fails.append(f"table {tid} has {len(gl)} guests for {t['capacity']} seats")
|
|
147
|
+
bad_tables.add(tid)
|
|
148
|
+
for grp in plan["together"]:
|
|
149
|
+
at = {table_of.get(g) for g in grp if g in table_of}
|
|
150
|
+
if len(at) > 1:
|
|
151
|
+
fails.append("must sit together but are split: " + ", ".join(f"{name(g)} ({table_of.get(g, 'unseated')})" for g in grp))
|
|
152
|
+
bad_tables.update(t for t in at if t)
|
|
153
|
+
for a, b in plan["apart"]:
|
|
154
|
+
if a in table_of and table_of.get(a) == table_of.get(b):
|
|
155
|
+
fails.append(f"{name(a)} and {name(b)} must be kept apart but both sit at table {table_of[a]}")
|
|
156
|
+
bad_tables.add(table_of[a])
|
|
157
|
+
for g, tid in table_of.items():
|
|
158
|
+
if "wheelchair" in guests[g]["needs"] and not tables[tid]["accessible"]:
|
|
159
|
+
fails.append(f"{name(g)} uses a wheelchair but table {tid} is not marked accessible")
|
|
160
|
+
bad_tables.add(tid)
|
|
161
|
+
for g, tid in plan["fixed"].items():
|
|
162
|
+
if table_of.get(g) != tid:
|
|
163
|
+
fails.append(f"{name(g)} has a fixed seat at table {tid} but sits at {table_of.get(g, 'no table')}")
|
|
164
|
+
bad_tables.add(tid)
|
|
165
|
+
|
|
166
|
+
biggest = max(t["capacity"] for t in tables.values())
|
|
167
|
+
parties = {}
|
|
168
|
+
for g, info in guests.items():
|
|
169
|
+
if info["party"]:
|
|
170
|
+
parties.setdefault(info["party"], []).append(g)
|
|
171
|
+
for party, members in sorted(parties.items()):
|
|
172
|
+
at = sorted({table_of[g] for g in members if g in table_of})
|
|
173
|
+
if len(at) > 1 and len(members) <= biggest:
|
|
174
|
+
warns.append(f"the {party} party ({len(members)}) is split across tables {', '.join(at)}")
|
|
175
|
+
for tid, gl in seats.items():
|
|
176
|
+
cap = tables[tid]["capacity"]
|
|
177
|
+
if not gl:
|
|
178
|
+
warns.append(f"table {tid} is empty")
|
|
179
|
+
elif len(gl) < cap / 2:
|
|
180
|
+
warns.append(f"table {tid} is under half full ({len(gl)} of {cap})")
|
|
181
|
+
in_group = {g for grp in plan["together"] for g in grp}
|
|
182
|
+
for g, tid in table_of.items():
|
|
183
|
+
p = guests[g]["party"]
|
|
184
|
+
alone = (not p or len(parties.get(p, [])) == 1) and g not in in_group
|
|
185
|
+
others = [o for o in seats[tid] if o != g]
|
|
186
|
+
other_parties = {guests[o]["party"] for o in others}
|
|
187
|
+
if alone and len(others) >= 2 and len(other_parties) == 1 and "" not in other_parties:
|
|
188
|
+
warns.append(f"{name(g)} came alone and is the only guest at table {tid} not in the {other_parties.pop()} party")
|
|
189
|
+
if plan["exits"]:
|
|
190
|
+
for g, tid in table_of.items():
|
|
191
|
+
t = tables[tid]
|
|
192
|
+
if "near exit" in guests[g]["needs"] and t["at"]:
|
|
193
|
+
d = min(math.dist(t["at"], e) for e in plan["exits"])
|
|
194
|
+
if d > exit_distance:
|
|
195
|
+
warns.append(f"{name(g)} needs to be near an exit but table {tid} is {d:.1f} m from the nearest")
|
|
196
|
+
return {"fails": fails, "warns": warns, "bad_tables": sorted(bad_tables), "table_of": table_of,
|
|
197
|
+
"seated": len(table_of), "guests": len(guests),
|
|
198
|
+
"seats": sum(t["capacity"] for t in tables.values())}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def table_rows(plan, r):
|
|
202
|
+
rows = []
|
|
203
|
+
for tid, t in plan["tables"].items():
|
|
204
|
+
gl = plan["seats"][tid]
|
|
205
|
+
diet = {}
|
|
206
|
+
for g in gl:
|
|
207
|
+
d = plan["guests"][g]["dietary"]
|
|
208
|
+
if d:
|
|
209
|
+
diet[d] = diet.get(d, 0) + 1
|
|
210
|
+
rows.append({"table": tid, "shape": t["shape"], "capacity": t["capacity"], "accessible": t["accessible"],
|
|
211
|
+
"seated": len(gl), "guests": [plan["guests"][g]["name"] for g in gl], "dietary": diet,
|
|
212
|
+
"ok": tid not in r["bad_tables"]})
|
|
213
|
+
return rows
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def report(plan, r):
|
|
217
|
+
out = [f"{r['seated']} of {r['guests']} guests seated, {len(plan['tables'])} tables, {r['seats']} seats."]
|
|
218
|
+
for row in table_rows(plan, r):
|
|
219
|
+
flags = ("accessible, " if row["accessible"] else "") + f"{row['seated']}/{row['capacity']}"
|
|
220
|
+
diet = ("; " + ", ".join(f"{n} {d}" for d, n in sorted(row["dietary"].items()))) if row["dietary"] else ""
|
|
221
|
+
out.append(f" {row['table']} ({row['shape']}, {flags}{diet}): {', '.join(row['guests']) or '-'}")
|
|
222
|
+
out += [f"FAIL {f}" for f in r["fails"]] + [f"WARN {w}" for w in r["warns"]]
|
|
223
|
+
out.append("PASS" if not r["fails"] else f"{len(r['fails'])} failure(s)")
|
|
224
|
+
return "\n".join(out) + "\n"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def esc(t):
|
|
228
|
+
return str(t).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def layout(plan):
|
|
232
|
+
"""Positions for every table: given ones, or a grid for tables without one."""
|
|
233
|
+
pos, n = {}, 0
|
|
234
|
+
for tid, t in plan["tables"].items():
|
|
235
|
+
if t["at"]:
|
|
236
|
+
pos[tid] = t["at"]
|
|
237
|
+
cols = max(1, math.ceil(math.sqrt(len(plan["tables"]))))
|
|
238
|
+
for tid, t in plan["tables"].items():
|
|
239
|
+
if tid not in pos:
|
|
240
|
+
pos[tid] = (2.5 + 4.0 * (n % cols), 2.5 + 4.0 * (n // cols))
|
|
241
|
+
n += 1
|
|
242
|
+
return pos
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def svg(plan, r, scale=40):
|
|
246
|
+
pos = layout(plan)
|
|
247
|
+
radius = lambda t: 0.45 + 0.09 * t["capacity"] # metres, so a 10-seat round is about 1.35 m
|
|
248
|
+
xs = [p[0] for p in pos.values()] + [e[0] for e in plan["exits"]]
|
|
249
|
+
ys = [p[1] for p in pos.values()] + [e[1] for e in plan["exits"]]
|
|
250
|
+
w, d = plan["room"] or (max(xs) + 3, max(ys) + 3)
|
|
251
|
+
W, H = w * scale + 80, d * scale + 110
|
|
252
|
+
P = lambda x, y: (40 + x * scale, 60 + y * scale)
|
|
253
|
+
out = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W:.0f} {H:.0f}" width="{W:.0f}" height="{H:.0f}" '
|
|
254
|
+
'font-family="Helvetica, Arial, sans-serif">',
|
|
255
|
+
f'<rect width="{W:.0f}" height="{H:.0f}" fill="#ffffff"/>',
|
|
256
|
+
f'<text x="40" y="30" font-size="16" font-weight="bold">Seating plan: {r["seated"]} of {r["guests"]} guests, '
|
|
257
|
+
f'{len(plan["tables"])} tables</text>',
|
|
258
|
+
f'<rect x="40" y="60" width="{w * scale:.0f}" height="{d * scale:.0f}" fill="#fafafa" stroke="#444" stroke-width="2"/>']
|
|
259
|
+
for i, e in enumerate(plan["exits"]):
|
|
260
|
+
x, y = P(*e)
|
|
261
|
+
out.append(f'<rect x="{x - 14:.0f}" y="{y - 6:.0f}" width="28" height="12" fill="#2e7d32"/>'
|
|
262
|
+
f'<text x="{x:.0f}" y="{y + 22:.0f}" font-size="10" text-anchor="middle" fill="#2e7d32">EXIT</text>')
|
|
263
|
+
for tid, t in plan["tables"].items():
|
|
264
|
+
x, y = P(*pos[tid])
|
|
265
|
+
rad = radius(t) * scale
|
|
266
|
+
bad = tid in r["bad_tables"]
|
|
267
|
+
stroke, fill = ("#c62828", "#ffebee") if bad else ("#455a64", "#eceff1")
|
|
268
|
+
if t["shape"] == "round":
|
|
269
|
+
out.append(f'<circle cx="{x:.0f}" cy="{y:.0f}" r="{rad:.0f}" fill="{fill}" stroke="{stroke}" stroke-width="{3 if bad else 1.5}"/>')
|
|
270
|
+
else:
|
|
271
|
+
out.append(f'<rect x="{x - rad * 1.4:.0f}" y="{y - rad * 0.6:.0f}" width="{rad * 2.8:.0f}" height="{rad * 1.2:.0f}" '
|
|
272
|
+
f'fill="{fill}" stroke="{stroke}" stroke-width="{3 if bad else 1.5}"/>')
|
|
273
|
+
gl = plan["seats"][tid]
|
|
274
|
+
mark = " ✗" if bad else ""
|
|
275
|
+
acc = " · accessible" if t["accessible"] else ""
|
|
276
|
+
out.append(f'<text x="{x:.0f}" y="{y - 2:.0f}" font-size="13" font-weight="bold" text-anchor="middle" '
|
|
277
|
+
f'fill="{stroke}">{esc(tid)}{mark}</text>'
|
|
278
|
+
f'<text x="{x:.0f}" y="{y + 13:.0f}" font-size="10" text-anchor="middle" fill="#555">{len(gl)}/{t["capacity"]}{acc}</text>')
|
|
279
|
+
rx, ry = (rad + 8, rad + 8) if t["shape"] == "round" else (rad * 1.4 + 10, rad * 0.6 + 10)
|
|
280
|
+
for k, g in enumerate(gl):
|
|
281
|
+
a = -math.pi / 2 + 2 * math.pi * k / max(len(gl), 1)
|
|
282
|
+
gx, gy = x + rx * math.cos(a), y + ry * math.sin(a)
|
|
283
|
+
anchor = "start" if math.cos(a) > 0.2 else ("end" if math.cos(a) < -0.2 else "middle")
|
|
284
|
+
out.append(f'<text x="{gx:.0f}" y="{gy + 3:.0f}" font-size="9" text-anchor="{anchor}" fill="#222">'
|
|
285
|
+
f'{esc(plan["guests"][g]["name"])}</text>')
|
|
286
|
+
ly = 60 + d * scale + 28
|
|
287
|
+
status = "PASS: every rule met" if not r["fails"] else f"{len(r['fails'])} rule(s) broken: tables marked ✗ in red"
|
|
288
|
+
out.append(f'<text x="40" y="{ly:.0f}" font-size="12" fill="{"#2e7d32" if not r["fails"] else "#c62828"}">{esc(status)}</text>')
|
|
289
|
+
out.append(f'<text x="40" y="{ly + 18:.0f}" font-size="10" fill="#666">Positions in metres, '
|
|
290
|
+
f'1 m = {scale} px · names run clockwise from the top of each table</text>')
|
|
291
|
+
out.append("</svg>")
|
|
292
|
+
return "\n".join(out) + "\n"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def main(argv=None):
|
|
296
|
+
ap = argparse.ArgumentParser(description="Check a seating plan against its rules and draw the room.")
|
|
297
|
+
ap.add_argument("seating")
|
|
298
|
+
ap.add_argument("--svg", help="write the room plan here")
|
|
299
|
+
ap.add_argument("--json", action="store_true", help="print the result as JSON")
|
|
300
|
+
ap.add_argument("--exit-distance", type=float, default=8.0,
|
|
301
|
+
help="metres from an exit that counts as near one (default 8)")
|
|
302
|
+
args = ap.parse_args(argv)
|
|
303
|
+
try:
|
|
304
|
+
with open(args.seating, encoding="utf-8") as fh:
|
|
305
|
+
plan = load(json.load(fh))
|
|
306
|
+
except (OSError, ValueError) as e: # BadInput and JSONDecodeError are ValueErrors
|
|
307
|
+
print(f"FAIL cannot read {args.seating}: {e}")
|
|
308
|
+
return 2
|
|
309
|
+
r = check(plan, args.exit_distance)
|
|
310
|
+
if args.json:
|
|
311
|
+
print(json.dumps({"pass": not r["fails"], "fails": r["fails"], "warns": r["warns"],
|
|
312
|
+
"seated": r["seated"], "guests": r["guests"], "tables": table_rows(plan, r)}, indent=1))
|
|
313
|
+
else:
|
|
314
|
+
print(report(plan, r), end="")
|
|
315
|
+
if args.svg:
|
|
316
|
+
with open(args.svg, "w", encoding="utf-8") as fh:
|
|
317
|
+
fh.write(svg(plan, r))
|
|
318
|
+
if not args.json:
|
|
319
|
+
print(f"\nWrote {args.svg}")
|
|
320
|
+
return 1 if r["fails"] else 0
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if __name__ == "__main__":
|
|
324
|
+
sys.exit(main())
|