skilldrop-cli 1.6.0 → 1.8.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 +3 -0
- package/catalogue.json +25 -2
- package/model-routing.json +20 -0
- package/package.json +1 -1
- package/packs/event-planner/pack.json +61 -0
- package/packs/event-planner/skills/run-of-show/SKILL.md +144 -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 +155 -0
- package/packs/event-planner/skills/seating-plan/evals/eval_queries.json +30 -0
- package/packs/event-planner/skills/seating-plan/evals/evals.json +164 -0
- package/packs/event-planner/skills/seating-plan/evals/files/1/guests.csv +31 -0
- package/packs/event-planner/skills/seating-plan/evals/files/10/hall_notes.txt +13 -0
- package/packs/event-planner/skills/seating-plan/evals/files/10/quiz_night_registrations.csv +67 -0
- package/packs/event-planner/skills/seating-plan/evals/files/11/notes_for_the_plan.txt +21 -0
- package/packs/event-planner/skills/seating-plan/evals/files/11/rsvp_list.csv +77 -0
- package/packs/event-planner/skills/seating-plan/evals/files/11/table_plan_draft.csv +78 -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/interior-designer/pack.json +60 -0
- package/packs/interior-designer/skills/furniture-layout/SKILL.md +182 -0
- package/packs/interior-designer/skills/furniture-layout/evals/eval_queries.json +11 -0
- package/packs/interior-designer/skills/furniture-layout/evals/evals.json +133 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/2/living-room-notes.md +24 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/3/office-brief.md +18 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/4/furniture_schedule.csv +15 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/4/site_survey.txt +22 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/5/layout_v2.csv +14 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/5/measurements.txt +16 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/6/access_consultant_email.txt +19 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/6/furniture_stock.csv +5 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/6/site_measurements.txt +14 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/7/room_214_layout.csv +11 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/7/room_and_brand_standard.txt +20 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/8/email_from_practice_manager.txt +44 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/8/furniture_we_are_keeping.csv +10 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/9/reading_room_schedule.csv +15 -0
- package/packs/interior-designer/skills/furniture-layout/evals/files/9/room_notes.txt +23 -0
- package/packs/interior-designer/skills/furniture-layout/examples/king-bed-bedroom.md +121 -0
- package/packs/interior-designer/skills/furniture-layout/examples/king-bed-bedroom.svg +57 -0
- package/packs/interior-designer/skills/furniture-layout/manifest.json +44 -0
- package/packs/interior-designer/skills/furniture-layout/scripts/layout_check.py +1130 -0
- package/packs/interior-designer/skills/furniture-layout/templates/layout.json +26 -0
- package/packs/operations-manager/pack.json +60 -0
- package/packs/operations-manager/skills/shift-roster/SKILL.md +169 -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,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.3",
|
|
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())
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"room": {"width": 14, "depth": 10},
|
|
3
|
+
"exits": [[0, 5], [14, 9]],
|
|
4
|
+
"tables": [
|
|
5
|
+
{"id": "Head", "capacity": 8, "shape": "rect", "at": [7, 1.5], "accessible": true},
|
|
6
|
+
{"id": "T1", "capacity": 10, "shape": "round", "at": [3, 5], "accessible": true},
|
|
7
|
+
{"id": "T2", "capacity": 10, "shape": "round", "at": [7, 5.5]},
|
|
8
|
+
{"id": "T3", "capacity": 10, "shape": "round", "at": [11, 5]}
|
|
9
|
+
],
|
|
10
|
+
"guests": [
|
|
11
|
+
{"id": "g1", "name": "Mira Osei", "party": "Couple"},
|
|
12
|
+
{"id": "g2", "name": "Tom Hale", "party": "Couple"},
|
|
13
|
+
{"id": "g3", "name": "Ruth Osei", "party": "Osei", "needs": ["wheelchair"], "dietary": "vegetarian"},
|
|
14
|
+
{"id": "g4", "name": "Kwame Osei", "party": "Osei"},
|
|
15
|
+
{"id": "g5", "name": "Dana Hale", "party": "Hale", "needs": ["near exit"]},
|
|
16
|
+
{"id": "g6", "name": "Pete Hale", "party": "Hale", "dietary": "gluten-free"},
|
|
17
|
+
{"id": "g7", "name": "Lena Brook", "party": ""}
|
|
18
|
+
],
|
|
19
|
+
"together": [["g3", "g4"]],
|
|
20
|
+
"apart": [["g4", "g6"]],
|
|
21
|
+
"fixed": {"g1": "Head", "g2": "Head"},
|
|
22
|
+
"assignments": {
|
|
23
|
+
"Head": ["g1", "g2"],
|
|
24
|
+
"T1": ["g3", "g4", "g7"],
|
|
25
|
+
"T2": [],
|
|
26
|
+
"T3": ["g5", "g6"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
"you_get": "A blueprint table and Mermaid diagram with fail points, waits, pains traced to causes, and 2–3 ranked fixes.",
|
|
58
58
|
"decide": "Which fix becomes committed work."
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
"skill": "locale-check",
|
|
62
|
+
"you_do": "Hand over the source strings and the translated files before release.",
|
|
63
|
+
"you_get": "Whether the release is safe to ship, and each failure by locale with the corrected string or a note for the translator."
|
|
64
|
+
},
|
|
60
65
|
{
|
|
61
66
|
"skill": "doc-critique",
|
|
62
67
|
"you_do": "Run it on any of the specs before you share them.",
|