skilldrop-cli 0.16.6 → 0.16.7
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 -1
- package/catalogue.json +10 -1
- package/model-routing.json +12 -0
- package/package.json +1 -1
- package/packs/ai-engineering/skills/ai-usage-policy/evals/evals.json +30 -0
- package/packs/ai-engineering/skills/ai-usage-policy/evals/files/2/data-handling-sheet.md +18 -0
- package/packs/ai-engineering/skills/ai-usage-policy/evals/files/2/it-notes.md +31 -0
- package/packs/ai-engineering/skills/ai-usage-policy/evals/files/2/md-email.md +29 -0
- package/packs/api-builder/skills/tool-use-schema-writer/evals/evals.json +29 -0
- package/packs/api-builder/skills/tool-use-schema-writer/evals/files/2/book_appointment.ts +64 -0
- package/packs/career/loops/apply/LOOP.md +88 -0
- package/packs/career/loops/apply/evals/eval_queries.json +9 -0
- package/packs/career/loops/apply/evals/evals.json +27 -0
- package/packs/career/loops/apply/loop.json +46 -0
- package/packs/career/pack.json +57 -0
- package/packs/career/skills/cover-letter/SKILL.md +132 -0
- package/packs/career/skills/cover-letter/evals/eval_queries.json +34 -0
- package/packs/career/skills/cover-letter/evals/evals.json +100 -0
- package/packs/career/skills/cover-letter/evals/files/1/posting.md +28 -0
- package/packs/career/skills/cover-letter/evals/files/1/resume.md +35 -0
- package/packs/career/skills/cover-letter/evals/files/2/posting.md +20 -0
- package/packs/career/skills/cover-letter/evals/files/2/resume.md +32 -0
- package/packs/career/skills/cover-letter/evals/files/4/posting.md +19 -0
- package/packs/career/skills/cover-letter/evals/files/4/resume.md +26 -0
- package/packs/career/skills/cover-letter/evals/files/5/posting.md +21 -0
- package/packs/career/skills/cover-letter/evals/files/5/resume.md +32 -0
- package/packs/career/skills/cover-letter/evals/files/6/posting.md +25 -0
- package/packs/career/skills/cover-letter/evals/files/6/resume.md +29 -0
- package/packs/career/skills/cover-letter/evals/files/7/posting.md +18 -0
- package/packs/career/skills/cover-letter/evals/files/7/resume.md +23 -0
- package/packs/career/skills/cover-letter/manifest.json +37 -0
- package/packs/career/skills/cover-letter/scripts/claim_check.py +247 -0
- package/packs/career/skills/job-fit-analysis/SKILL.md +136 -0
- package/packs/career/skills/job-fit-analysis/evals/eval_queries.json +34 -0
- package/packs/career/skills/job-fit-analysis/evals/evals.json +76 -0
- package/packs/career/skills/job-fit-analysis/evals/files/1/posting.md +28 -0
- package/packs/career/skills/job-fit-analysis/evals/files/1/resume.md +35 -0
- package/packs/career/skills/job-fit-analysis/evals/files/2/posting.md +20 -0
- package/packs/career/skills/job-fit-analysis/evals/files/2/resume.md +32 -0
- package/packs/career/skills/job-fit-analysis/evals/files/4/posting.md +25 -0
- package/packs/career/skills/job-fit-analysis/evals/files/4/resume.md +29 -0
- package/packs/career/skills/job-fit-analysis/evals/files/5/posting.md +18 -0
- package/packs/career/skills/job-fit-analysis/evals/files/5/resume.md +23 -0
- package/packs/career/skills/job-fit-analysis/manifest.json +31 -0
- package/packs/career/skills/resume-tailor/SKILL.md +116 -0
- package/packs/career/skills/resume-tailor/evals/eval_queries.json +34 -0
- package/packs/career/skills/resume-tailor/evals/evals.json +105 -0
- package/packs/career/skills/resume-tailor/evals/files/1/posting.md +26 -0
- package/packs/career/skills/resume-tailor/evals/files/1/resume.md +32 -0
- package/packs/career/skills/resume-tailor/evals/files/2/posting.md +19 -0
- package/packs/career/skills/resume-tailor/evals/files/2/resume.md +26 -0
- package/packs/career/skills/resume-tailor/evals/files/3/posting.md +21 -0
- package/packs/career/skills/resume-tailor/evals/files/3/resume.md +32 -0
- package/packs/career/skills/resume-tailor/evals/files/4/posting.md +28 -0
- package/packs/career/skills/resume-tailor/evals/files/4/resume.md +35 -0
- package/packs/career/skills/resume-tailor/evals/files/5/posting.md +20 -0
- package/packs/career/skills/resume-tailor/evals/files/5/resume.md +32 -0
- package/packs/career/skills/resume-tailor/evals/files/6/posting.md +25 -0
- package/packs/career/skills/resume-tailor/evals/files/6/resume.md +29 -0
- package/packs/career/skills/resume-tailor/evals/files/7/posting.md +18 -0
- package/packs/career/skills/resume-tailor/evals/files/7/resume.md +23 -0
- package/packs/career/skills/resume-tailor/manifest.json +37 -0
- package/packs/career/skills/resume-tailor/scripts/claim_check.py +247 -0
- package/packs/data-analytics/skills/metric-definition/evals/evals.json +30 -0
- package/packs/data-analytics/skills/metric-definition/evals/files/2/tables.md +42 -0
- package/packs/data-analytics/skills/metric-definition/evals/files/2/thread.md +40 -0
- package/packs/dev-team/skills/contribution-wizard/evals/evals.json +29 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/AGENTS.md +9 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/CATALOG.md +18 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/CONTRIBUTING.md +45 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/OWNERS.md +7 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/detention-claim-letter/INSTRUCTIONS.md +20 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/detention-claim-letter/skill.yaml +11 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/detention-claim-letter/tests/cases.yaml +25 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/rate-con-extractor/INSTRUCTIONS.md +20 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/rate-con-extractor/skill.yaml +8 -0
- package/packs/dev-team/skills/contribution-wizard/evals/files/2/skills/rate-con-extractor/tests/cases.yaml +32 -0
- package/packs/dev-team/skills/devils-advocate/evals/evals.json +29 -0
- package/packs/dev-team/skills/devils-advocate/evals/files/2/signups.py +116 -0
- package/packs/dev-team/skills/devils-advocate/evals/files/2/test_signups.py +69 -0
- package/packs/dev-team/skills/release-notes/evals/evals.json +29 -0
- package/packs/dev-team/skills/release-notes/evals/files/2/commits.txt +35 -0
- package/packs/research/skills/source-synthesis/evals/evals.json +30 -0
- package/packs/research/skills/source-synthesis/evals/files/2/badge-data.md +27 -0
- package/packs/research/skills/source-synthesis/evals/files/2/director-email.md +16 -0
- package/packs/research/skills/source-synthesis/evals/files/2/exit-interviews.md +17 -0
- package/packs/research/skills/source-synthesis/evals/files/2/practice-weekly-article.md +21 -0
- package/packs/research/skills/source-synthesis/evals/files/2/staff-survey-summary.md +41 -0
- package/packs/solution-architect/skills/db-schema-design/evals/evals.json +29 -0
- package/packs/solution-architect/skills/db-schema-design/evals/files/2/current_tables.sql +29 -0
- package/packs/solution-architect/skills/db-schema-design/evals/files/2/notes.md +53 -0
- package/packs/solution-architect/skills/design-doc/evals/evals.json +29 -0
- package/packs/solution-architect/skills/design-doc/evals/files/2/brief.md +18 -0
- package/packs/solution-architect/skills/design-doc/evals/files/2/eng-notes.md +30 -0
- package/packs/solution-architect/skills/tech-comparison-matrix/evals/evals.json +29 -0
- package/packs/solution-architect/skills/tech-comparison-matrix/evals/files/2/requirements.md +20 -0
- package/packs/solution-architect/skills/tech-comparison-matrix/evals/files/2/vendor-notes.md +29 -0
- package/packs/sre-oncall/skills/postmortem-generator/evals/evals.json +30 -0
- package/packs/sre-oncall/skills/postmortem-generator/evals/files/2/pager-export.txt +22 -0
- package/packs/sre-oncall/skills/postmortem-generator/evals/files/2/slack-thread.txt +28 -0
- package/packs/stakeholder-comms/skills/guide-builder/evals/evals.json +29 -0
- package/packs/stakeholder-comms/skills/guide-builder/evals/files/2/api-notes.md +44 -0
- package/packs/stakeholder-comms/skills/guide-builder/evals/files/2/sample-booking.json +19 -0
- package/profiles.json +2 -2
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Finance Analyst
|
|
2
|
+
|
|
3
|
+
**Orrell Leisure Group** · Birmingham · £36,000 – £40,000
|
|
4
|
+
|
|
5
|
+
Orrell runs 22 gyms across the Midlands. The Finance Analyst reports to the Financial Controller and produces the monthly management pack for site managers.
|
|
6
|
+
|
|
7
|
+
## What you'll do
|
|
8
|
+
|
|
9
|
+
- Build the monthly management accounts pack: variance to budget by site, with commentary
|
|
10
|
+
- Own the month-end close for payroll and utilities accruals
|
|
11
|
+
- Maintain the site-level budget model and reforecast each quarter
|
|
12
|
+
- Build dashboards in Power BI for the operations team
|
|
13
|
+
|
|
14
|
+
## What we need
|
|
15
|
+
|
|
16
|
+
- Part-qualified ACCA or CIMA, or AAT Level 4 complete
|
|
17
|
+
- At least 2 years in a management accounts or analyst role
|
|
18
|
+
- Advanced Excel (pivot tables, lookups, modelling)
|
|
19
|
+
- Experience preparing variance analysis and budget commentary
|
|
20
|
+
- Experience with Power BI or a similar reporting tool
|
|
21
|
+
|
|
22
|
+
## Nice to have
|
|
23
|
+
|
|
24
|
+
- Experience in a multi-site business
|
|
25
|
+
- Experience with Sage or NetSuite
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Grace Mbeki
|
|
2
|
+
|
|
3
|
+
Birmingham · grace.mbeki@example.com · 07700 900455
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
**Accounts Payable Clerk** — Halden Building Supplies, Birmingham
|
|
8
|
+
February 2021 – present
|
|
9
|
+
|
|
10
|
+
- Process around 400 supplier invoices a month in Sage 200, matching each to its purchase order and delivery note.
|
|
11
|
+
- Reconcile 60 supplier statements each month and chase the differences.
|
|
12
|
+
- Prepare the fortnightly payment run for the Finance Manager to approve.
|
|
13
|
+
- Built an Excel workbook (pivot tables and lookups) that lists invoices on hold by reason; the purchasing team now uses it every week.
|
|
14
|
+
- Support month-end by posting accruals for goods received and not yet invoiced.
|
|
15
|
+
|
|
16
|
+
**Finance Administrator** — Corrin Dental Group, Solihull
|
|
17
|
+
September 2018 – January 2021
|
|
18
|
+
|
|
19
|
+
- Raised sales invoices, banked receipts and kept the petty cash for four practices.
|
|
20
|
+
|
|
21
|
+
## Qualifications
|
|
22
|
+
|
|
23
|
+
- AAT Level 3 Diploma in Accounting, 2020
|
|
24
|
+
- Studying AAT Level 4; two of five units passed
|
|
25
|
+
- A levels: Mathematics (B), Economics (C), 2018
|
|
26
|
+
|
|
27
|
+
## Tools
|
|
28
|
+
|
|
29
|
+
Sage 200, Excel, Microsoft Teams
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Head Chef
|
|
2
|
+
Calder House Hotel, Peebles - 62 bedrooms, restaurant and events
|
|
3
|
+
|
|
4
|
+
You will lead the kitchen for our 120-cover restaurant and for weddings and events of up to 180 guests.
|
|
5
|
+
|
|
6
|
+
Requirements
|
|
7
|
+
- Level 3 Food Safety (Supervising Food Safety in Catering)
|
|
8
|
+
- Experience as a head chef, or as a sous chef with full responsibility for a kitchen
|
|
9
|
+
- Experience leading a team of 12 or more
|
|
10
|
+
- Menu design and costing to a gross profit target of 70%
|
|
11
|
+
- Experience of banqueting and events
|
|
12
|
+
- Stock control and supplier management
|
|
13
|
+
|
|
14
|
+
Desirable
|
|
15
|
+
- Experience in a hotel kitchen
|
|
16
|
+
- Experience with Procure Wizard or a similar ordering system
|
|
17
|
+
|
|
18
|
+
Salary 48,000 pounds plus tronc. Live-in available.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
LEO TANAKA
|
|
2
|
+
Edinburgh | leo.tanaka@example.com | 07700 900271
|
|
3
|
+
|
|
4
|
+
Sous Chef, The Larder at Queensgate (80-cover restaurant), Edinburgh
|
|
5
|
+
May 2022 - present
|
|
6
|
+
- Run the kitchen on the head chef's two days off each week.
|
|
7
|
+
- Lead a brigade of 6 on service.
|
|
8
|
+
- Write and cost the weekly specials; do the fish and vegetable orders.
|
|
9
|
+
- Trained 3 commis chefs, one now a chef de partie.
|
|
10
|
+
- Keep the allergen matrix up to date for every menu change.
|
|
11
|
+
- Environmental health inspection in 2025: rated 5.
|
|
12
|
+
|
|
13
|
+
Chef de Partie, Harbour Rooms Hotel, Leith
|
|
14
|
+
March 2019 - April 2022
|
|
15
|
+
- Ran the sauce section; covered banqueting for weddings in summer.
|
|
16
|
+
|
|
17
|
+
Commis Chef, Harbour Rooms Hotel, Leith
|
|
18
|
+
August 2017 - March 2019
|
|
19
|
+
|
|
20
|
+
TRAINING
|
|
21
|
+
SVQ Level 3 Professional Cookery, Edinburgh College, 2019
|
|
22
|
+
Level 2 Food Safety and Hygiene for Catering, renewed 2024
|
|
23
|
+
Allergen awareness training, 2024
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "resume-tailor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tailor a resume or CV to one job posting without adding anything that isn't true: reorder, cut and reword what the person has already done so the posting's needs are answered first, keep every employer, title, date and number as it was, and report what the posting asks for that the resume cannot claim. A script lists every figure and name in the draft that the original doesn't contain. Works for any occupation and level. Use when the user says \"tailor my resume for this job\", \"adapt my CV to this posting\", \"rewrite my resume for this role\", or pastes a job description next to their resume.",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": {
|
|
7
|
+
"npm": [],
|
|
8
|
+
"pip": []
|
|
9
|
+
},
|
|
10
|
+
"env": {
|
|
11
|
+
"required": [],
|
|
12
|
+
"optional": []
|
|
13
|
+
},
|
|
14
|
+
"related": [
|
|
15
|
+
"cover-letter",
|
|
16
|
+
"doc-critique",
|
|
17
|
+
"job-fit-analysis"
|
|
18
|
+
],
|
|
19
|
+
"tags": [
|
|
20
|
+
"resume",
|
|
21
|
+
"cv",
|
|
22
|
+
"job-application",
|
|
23
|
+
"tailoring",
|
|
24
|
+
"career",
|
|
25
|
+
"job-search"
|
|
26
|
+
],
|
|
27
|
+
"permissions": {
|
|
28
|
+
"network": [],
|
|
29
|
+
"commands": [],
|
|
30
|
+
"files": "named-paths",
|
|
31
|
+
"notes": "Reads the draft, source and posting files it is given. Writes nothing."
|
|
32
|
+
},
|
|
33
|
+
"model": {
|
|
34
|
+
"tier": "standard",
|
|
35
|
+
"rationale": "Reorders and rewords existing content against a posting, with a script listing every figure and name the original doesn't contain."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List what a tailored resume or cover letter says that the candidate's own material doesn't.
|
|
3
|
+
|
|
4
|
+
python3 claim_check.py DRAFT --source RESUME [--source NOTES ...] [--posting POSTING] [--json]
|
|
5
|
+
|
|
6
|
+
DRAFT is the text you are about to send. Each --source is something the candidate wrote or
|
|
7
|
+
told you: the original resume, notes from the conversation. --posting is the job posting.
|
|
8
|
+
|
|
9
|
+
It reports four kinds of finding, each with the line it is on:
|
|
10
|
+
|
|
11
|
+
number a figure in the draft that no source contains ("35%", "$2M", "team of 8")
|
|
12
|
+
name a named thing in the draft that no source contains: a tool, an employer, a
|
|
13
|
+
certification, a degree ("Kubernetes", "Epic", "Green Belt")
|
|
14
|
+
combination capitalised words that each appear in a source but never together
|
|
15
|
+
("Senior Manager" when the source has "Senior Analyst" and "Office Manager")
|
|
16
|
+
borrowed wording the draft shares with the posting and no source uses. Fine when the
|
|
17
|
+
source shows the same thing in other words; a false claim when it doesn't.
|
|
18
|
+
|
|
19
|
+
A name or number that the posting contains is marked "in posting": right when the draft is
|
|
20
|
+
talking about the employer, wrong when it is claimed as the candidate's own.
|
|
21
|
+
|
|
22
|
+
It reads text, so it cannot tell a true claim from a false one. It tells you where to look.
|
|
23
|
+
Exit status: 0 nothing found, 1 findings, 2 a file could not be read. Standard library only.
|
|
24
|
+
"""
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import re
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
MULT = {"k": 1e3, "thousand": 1e3, "m": 1e6, "mm": 1e6, "million": 1e6,
|
|
31
|
+
"bn": 1e9, "b": 1e9, "billion": 1e9}
|
|
32
|
+
NUMBER = re.compile(r"(?<![\w.])(\d[\d,]*(?:\.\d+)?)(?:(k|mm|m|bn|b)\b|\s?(thousand|million|billion)\b)?"
|
|
33
|
+
r"(\s?%|\s?percent\b|\s?per cent\b)?", re.I)
|
|
34
|
+
WORD = re.compile(r"\.?[A-Za-z][A-Za-z0-9+#']*(?:\.[A-Za-z0-9]+)*")
|
|
35
|
+
LIST_MARK = re.compile(r"^\s*(?:[-*•–>]+|\d+[.)]|#+|\|)\s*")
|
|
36
|
+
LINK = re.compile(r"\[([^\]]*)\]\([^)]*\)")
|
|
37
|
+
SUFFIXES = ("ations", "ation", "ments", "ment", "ings", "ing", "ers", "ies", "ed", "er", "es", "s", "ly")
|
|
38
|
+
|
|
39
|
+
# Words that start a line or a sentence with a capital letter and name nothing.
|
|
40
|
+
PLAIN = set("""
|
|
41
|
+
a an and are as at be been both but by for from had has have he her his i if in into is it its
|
|
42
|
+
me my no not of on or our she so that the their them then there these they this to up us was
|
|
43
|
+
we were what when where which who why with you your dear sincerely regards kind best yours
|
|
44
|
+
faithfully hiring manager team committee recruiter sir madam re subject date enclosed
|
|
45
|
+
summary profile objective experience employment work history education skills tools
|
|
46
|
+
certifications certification qualifications training projects awards languages interests
|
|
47
|
+
references available request professional career key core technical additional other
|
|
48
|
+
volunteering volunteer contact details achievements highlights about present current
|
|
49
|
+
january february march april may june july august september october november december
|
|
50
|
+
jan feb mar apr jun jul aug sep sept oct nov dec monday tuesday wednesday thursday friday
|
|
51
|
+
saturday sunday full part time application applying while although though because since
|
|
52
|
+
during after before however also please thank thanks between over across within having most each every one two three
|
|
53
|
+
""".split())
|
|
54
|
+
DATE_LINE = re.compile(r"^\s*(?:\d{1,2}(?:st|nd|rd|th)?\s+)?[A-Z][a-z]{2,8}\.?\s+(?:\d{1,2}(?:st|nd|rd|th)?,?\s+)?\d{4}\s*$")
|
|
55
|
+
# The joining words allowed inside a capitalised phrase: "Head of Geography".
|
|
56
|
+
JOINERS = {"of", "and", "for", "the", "in", "at", "&", "de"}
|
|
57
|
+
STOP = PLAIN | set("""
|
|
58
|
+
able across after also been before being between can could did does doing each every experience
|
|
59
|
+
experienced including into just like more most much need needs over role roles some such than
|
|
60
|
+
them through under using very well will within without work worked working would years year
|
|
61
|
+
have with that this from your their about must should strong good great team teams including
|
|
62
|
+
ability skills skill knowledge new help helps support supports ensure make makes
|
|
63
|
+
""".split())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def read(path):
|
|
67
|
+
try:
|
|
68
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
69
|
+
return f.read()
|
|
70
|
+
except OSError as e:
|
|
71
|
+
print("claim_check: cannot read %s: %s" % (path, e.strerror), file=sys.stderr)
|
|
72
|
+
sys.exit(2)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def clean(line):
|
|
76
|
+
"""A line with its markdown removed, so `**Go**` and `Go` read the same."""
|
|
77
|
+
line = LINK.sub(r"\1", line)
|
|
78
|
+
line = line.replace("’", "'").replace("‘", "'").replace("–", " - ").replace("—", " - ")
|
|
79
|
+
return re.sub(r"[*_`~]", "", line)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def numbers(text):
|
|
83
|
+
"""{(value, is_percent)} for every figure in the text. "$2M" and "2 million" are one value."""
|
|
84
|
+
found = set()
|
|
85
|
+
for m in NUMBER.finditer(text):
|
|
86
|
+
try:
|
|
87
|
+
value = float(m.group(1).replace(",", ""))
|
|
88
|
+
except ValueError:
|
|
89
|
+
continue
|
|
90
|
+
unit = (m.group(2) or m.group(3) or "").lower()
|
|
91
|
+
found.add((value * MULT.get(unit, 1), bool(m.group(4))))
|
|
92
|
+
return found
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def stem(word):
|
|
96
|
+
w = word.lower().strip("'.")
|
|
97
|
+
if w.endswith("'s"):
|
|
98
|
+
w = w[:-2]
|
|
99
|
+
for s in SUFFIXES:
|
|
100
|
+
if w.endswith(s) and len(w) - len(s) >= 4:
|
|
101
|
+
return w[: -len(s)]
|
|
102
|
+
return w
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def key(word):
|
|
106
|
+
"""How a name is compared: case, dots, a plural, a possessive or a contraction don't matter."""
|
|
107
|
+
w = word.lower().strip("'")
|
|
108
|
+
w = re.sub(r"'(?:s|m|d|ve|ll|re|t)$", "", w)
|
|
109
|
+
w = w.replace(".", "") or w
|
|
110
|
+
return w[:-1] if len(w) > 4 and w.endswith("s") and not w.endswith("ss") else w
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def vocabulary(text):
|
|
114
|
+
names, stems = set(), set()
|
|
115
|
+
for line in text.splitlines():
|
|
116
|
+
for w in WORD.findall(clean(line)):
|
|
117
|
+
names.add(key(w))
|
|
118
|
+
stems.add(stem(w))
|
|
119
|
+
flat = " ".join(key(w) for line in text.splitlines() for w in WORD.findall(clean(line)))
|
|
120
|
+
return names, stems, " " + flat + " "
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def looks_named(word):
|
|
124
|
+
"""True for a word that is a name whatever its position: AWS, JavaScript, C++, k8s, .NET."""
|
|
125
|
+
body = word.lstrip(".")
|
|
126
|
+
return bool(re.search(r"[A-Z]", body[1:]) or re.search(r"[0-9+#]", body) or word.startswith("."))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def phrases(line):
|
|
130
|
+
"""Runs of capitalised or name-shaped words in one line, as lists of words.
|
|
131
|
+
|
|
132
|
+
The first word of a line or sentence is capitalised by grammar, not because it is a name,
|
|
133
|
+
so it only counts when it is name-shaped itself or opens a longer capitalised run."""
|
|
134
|
+
text = LIST_MARK.sub("", clean(line))
|
|
135
|
+
out, run, start_of_sentence = [], [], True
|
|
136
|
+
tokens = re.findall(r"\.?[A-Za-z][A-Za-z0-9+#']*(?:\.[A-Za-z0-9]+)*|&|[.!?;:]\s|[,|·/()]|\s-\s", text + " ")
|
|
137
|
+
for i, tok in enumerate(tokens):
|
|
138
|
+
if not (tok[0].isalpha() or tok[0] in ".&") or tok.strip() in (".", "!", "?", ";", ":"):
|
|
139
|
+
if run:
|
|
140
|
+
out.append(run)
|
|
141
|
+
run = []
|
|
142
|
+
start_of_sentence = tok.strip() in (".", "!", "?")
|
|
143
|
+
continue
|
|
144
|
+
if "'" in tok and key(tok) in PLAIN: # I'm, I've, we'd
|
|
145
|
+
tok = key(tok)
|
|
146
|
+
capital = tok[0].isupper() or looks_named(tok)
|
|
147
|
+
if capital and not (start_of_sentence and not looks_named(tok) and tok.lower() in PLAIN):
|
|
148
|
+
run.append((tok, start_of_sentence))
|
|
149
|
+
elif run and tok.lower() in JOINERS and i + 1 < len(tokens) and tokens[i + 1][:1].isupper():
|
|
150
|
+
run.append((tok, False))
|
|
151
|
+
else:
|
|
152
|
+
if run:
|
|
153
|
+
out.append(run)
|
|
154
|
+
run = []
|
|
155
|
+
start_of_sentence = False
|
|
156
|
+
if run:
|
|
157
|
+
out.append(run)
|
|
158
|
+
cleaned = []
|
|
159
|
+
for r in out:
|
|
160
|
+
# A lone sentence-opening word ("Led", "Built") is grammar, not a name.
|
|
161
|
+
if len(r) == 1 and r[0][1] and not looks_named(r[0][0]):
|
|
162
|
+
continue
|
|
163
|
+
# A sentence-opening verb in front of a name ("Led AWS migration") is dropped from the run.
|
|
164
|
+
if len(r) > 1 and r[0][1] and not looks_named(r[0][0]) and r[0][0].lower() not in PLAIN:
|
|
165
|
+
r = r[1:] if r[0][0].lower().endswith(("ed", "ing")) else r
|
|
166
|
+
words = [w for w, _ in r]
|
|
167
|
+
while words and words[-1].lower() in JOINERS:
|
|
168
|
+
words.pop()
|
|
169
|
+
if words:
|
|
170
|
+
cleaned.append(words)
|
|
171
|
+
return cleaned
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def check(draft, sources, posting):
|
|
175
|
+
src_names, src_stems, src_flat = vocabulary("\n".join(sources))
|
|
176
|
+
src_numbers = numbers("\n".join(clean(s) for s in sources))
|
|
177
|
+
post_names, post_stems, _ = vocabulary(posting or "")
|
|
178
|
+
post_numbers = numbers(clean(posting or ""))
|
|
179
|
+
findings, seen = [], set()
|
|
180
|
+
|
|
181
|
+
def add(kind, what, n, line, in_posting=False):
|
|
182
|
+
if (kind, what.lower()) in seen:
|
|
183
|
+
return
|
|
184
|
+
seen.add((kind, what.lower()))
|
|
185
|
+
findings.append({"kind": kind, "text": what, "line": n, "context": line.strip()[:160],
|
|
186
|
+
"in_posting": in_posting})
|
|
187
|
+
|
|
188
|
+
for n, raw in enumerate(draft.splitlines(), 1):
|
|
189
|
+
line = clean(raw)
|
|
190
|
+
if not line.strip() or DATE_LINE.match(LIST_MARK.sub("", line)):
|
|
191
|
+
continue # a letter's own date line claims nothing
|
|
192
|
+
body = LIST_MARK.sub("", line)
|
|
193
|
+
for m in NUMBER.finditer(body):
|
|
194
|
+
got = next(iter(numbers(m.group(0))), None)
|
|
195
|
+
if got and got not in src_numbers:
|
|
196
|
+
add("number", m.group(0).strip(), n, raw, got in post_numbers)
|
|
197
|
+
for words in phrases(raw):
|
|
198
|
+
content = [w for w in words if w.lower() not in JOINERS]
|
|
199
|
+
missing = [w for w in content if key(w) not in src_names and w.lower() not in PLAIN]
|
|
200
|
+
if missing:
|
|
201
|
+
add("name", " ".join(words), n, raw, all(key(w) in post_names for w in missing))
|
|
202
|
+
elif len(content) > 1 and all(w.lower() not in PLAIN for w in content):
|
|
203
|
+
flat = " ".join(key(w) for w in words)
|
|
204
|
+
if " " + flat + " " not in src_flat:
|
|
205
|
+
add("combination", " ".join(words), n, raw)
|
|
206
|
+
if posting:
|
|
207
|
+
for w in WORD.findall(body):
|
|
208
|
+
s = stem(w)
|
|
209
|
+
if (len(s) >= 4 and w.lower() not in STOP and s in post_stems and s not in src_stems
|
|
210
|
+
and key(w) not in src_names and not w[0].isupper() and not looks_named(w)):
|
|
211
|
+
add("borrowed", w.lower(), n, raw, True)
|
|
212
|
+
return findings
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def main():
|
|
216
|
+
ap = argparse.ArgumentParser(description="List what a draft says that the candidate's own material doesn't.")
|
|
217
|
+
ap.add_argument("draft")
|
|
218
|
+
ap.add_argument("--source", action="append", required=True, metavar="FILE",
|
|
219
|
+
help="the candidate's own resume or notes; repeat for more than one")
|
|
220
|
+
ap.add_argument("--posting", metavar="FILE", help="the job posting")
|
|
221
|
+
ap.add_argument("--json", action="store_true")
|
|
222
|
+
a = ap.parse_args()
|
|
223
|
+
|
|
224
|
+
findings = check(read(a.draft), [read(p) for p in a.source], read(a.posting) if a.posting else None)
|
|
225
|
+
if a.json:
|
|
226
|
+
print(json.dumps({"draft": a.draft, "findings": findings}, indent=1))
|
|
227
|
+
return 1 if findings else 0
|
|
228
|
+
if not findings:
|
|
229
|
+
print("PASS %s: every figure and name is in the source material." % a.draft)
|
|
230
|
+
return 0
|
|
231
|
+
labels = {"number": "Figures not in the source", "name": "Names not in the source",
|
|
232
|
+
"combination": "Words the source has, but not together",
|
|
233
|
+
"borrowed": "Wording taken from the posting"}
|
|
234
|
+
print("REVIEW %s: %d item(s) to check against the source material." % (a.draft, len(findings)))
|
|
235
|
+
for kind in ("number", "name", "combination", "borrowed"):
|
|
236
|
+
rows = [f for f in findings if f["kind"] == kind]
|
|
237
|
+
if not rows:
|
|
238
|
+
continue
|
|
239
|
+
print("\n%s (%d)" % (labels[kind], len(rows)))
|
|
240
|
+
for f in rows:
|
|
241
|
+
note = " [in posting]" if f["in_posting"] and kind != "borrowed" else ""
|
|
242
|
+
print(" line %d: %s%s\n %s" % (f["line"], f["text"], note, f["context"]))
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if __name__ == "__main__":
|
|
247
|
+
sys.exit(main())
|
|
@@ -14,6 +14,36 @@
|
|
|
14
14
|
"Includes at least three runnable sanity checks, including a known-answer fixture or a reconciliation with billing",
|
|
15
15
|
"Does not invent tables, columns or an owner not given in the prompt; anything assumed is tagged [assumption] and listed under open decisions"
|
|
16
16
|
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 2,
|
|
20
|
+
"held_out": "2026-10-05",
|
|
21
|
+
"prompt": "Finance and CX keep turning up to the Monday meeting with different refund rates and it's getting silly. I've pasted the Slack thread into thread.md and the table descriptions into tables.md. Can you write down one proper definition of refund rate that we can all use, with the SQL? We're on Postgres.",
|
|
22
|
+
"assertions": [
|
|
23
|
+
"It separates the share of orders refunded from the share of revenue refunded as two differently named measures, or picks one as 'refund rate' and names the other as a separate measure; it does not blend them into one formula",
|
|
24
|
+
"It points out that 312 refunded orders out of 7,480 is about 4.2%, not the 4.6% quoted in the thread",
|
|
25
|
+
"The order-based SQL counts each refunded order once (COUNT DISTINCT on order_id, EXISTS, or refunds aggregated per order before joining), so orders with several refund rows are not double-counted",
|
|
26
|
+
"It states one rule for which month a refund belongs to (the order's month or the refund's month) and says what happens to a February order refunded in March",
|
|
27
|
+
"It says a single money-based rate across both stores cannot be computed from these tables as they stand because there is no exchange-rate data, and it does not invent an exchange rate or an exchange-rate table",
|
|
28
|
+
"Test orders are excluded with a condition on orders.is_test, and staff orders are flagged as not identifiable from the listed columns; it does not use a staff flag column that does not exist",
|
|
29
|
+
"Month boundaries are set in UK time (Europe/London) against timestamps stored in UTC",
|
|
30
|
+
"It lists whether cancelled orders count in the denominator and who owns the metric as open decisions, without naming an owner the thread did not name"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": 3,
|
|
35
|
+
"held_out": "2026-10-05",
|
|
36
|
+
"prompt": "need a proper definition of 'time to first response' for our support team. we use a helpdesk tool and I can export tickets to a spreadsheet. my boss wants it on the weekly report. there's 8 agents. not sure if weekends should count, we don't work them",
|
|
37
|
+
"assertions": [
|
|
38
|
+
"It delivers a written definition in the reply and does not stop at a list of questions",
|
|
39
|
+
"It defines the start and the end of the clock (ticket created to the first reply from a human agent) and says automatic acknowledgement emails do not count as the first response, or flags that as a decision",
|
|
40
|
+
"Whether weekends and out-of-hours time count is given as an open decision with a recommended default or with both versions described; it is not decided silently",
|
|
41
|
+
"It says which statistic goes on the report (median, mean or a percentile), not just 'average' left undefined",
|
|
42
|
+
"It says how tickets that have had no reply yet when the report is run are treated",
|
|
43
|
+
"It says which week a ticket is counted in (the week it was created or the week it was answered)",
|
|
44
|
+
"Any export column names it uses are marked as assumed, since no export layout was given",
|
|
45
|
+
"It does not state a response-time target or SLA figure as the team's own, since none was given"
|
|
46
|
+
]
|
|
17
47
|
}
|
|
18
48
|
]
|
|
19
49
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Tallow & Thyme: tables (Postgres 15)
|
|
2
|
+
|
|
3
|
+
All timestamps are `timestamptz`, stored in UTC. The business reports on UK time.
|
|
4
|
+
|
|
5
|
+
## orders
|
|
6
|
+
|
|
7
|
+
| Column | Type | Notes |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| order_id | bigint | primary key |
|
|
10
|
+
| customer_id | bigint | references customers |
|
|
11
|
+
| store | text | 'uk' or 'eu' |
|
|
12
|
+
| currency | text | 'GBP' for the uk store, 'EUR' for the eu store |
|
|
13
|
+
| gross_amount_minor | integer | order total in pence (GBP) or cents (EUR) |
|
|
14
|
+
| placed_at | timestamptz | when the order was placed |
|
|
15
|
+
| status | text | 'placed', 'delivered', 'cancelled' |
|
|
16
|
+
| is_test | boolean | true for orders made by the test harness |
|
|
17
|
+
|
|
18
|
+
## refunds
|
|
19
|
+
|
|
20
|
+
| Column | Type | Notes |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| refund_id | bigint | primary key |
|
|
23
|
+
| order_id | bigint | references orders |
|
|
24
|
+
| amount_minor | integer | in the same currency as the order |
|
|
25
|
+
| reason | text | free text typed by the CX agent |
|
|
26
|
+
| created_at | timestamptz | when the refund was issued |
|
|
27
|
+
|
|
28
|
+
An order can have more than one refund row: a missing ingredient is refunded, then a second
|
|
29
|
+
problem with the same box is refunded later. About 1 in 9 refunded orders has two or more
|
|
30
|
+
rows.
|
|
31
|
+
|
|
32
|
+
## customers
|
|
33
|
+
|
|
34
|
+
| Column | Type | Notes |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| customer_id | bigint | primary key |
|
|
37
|
+
| email | text | |
|
|
38
|
+
| created_at | timestamptz | |
|
|
39
|
+
|
|
40
|
+
## Not in the database
|
|
41
|
+
|
|
42
|
+
There is no exchange-rate table. Finance converts EUR to GBP by hand in a spreadsheet.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# #ops-metrics, exported 3 March 2026
|
|
2
|
+
|
|
3
|
+
**Hattie Blum** (CX lead) 09:12
|
|
4
|
+
Feb refund rate is 4.6%. 312 refunded orders out of 7,480 orders.
|
|
5
|
+
|
|
6
|
+
**Rupert Osei** (Finance) 09:20
|
|
7
|
+
I get 3.5%. £9,870 refunded on £284,300 gross.
|
|
8
|
+
|
|
9
|
+
**Hattie Blum** 09:24
|
|
10
|
+
Well mine is orders, yours is money. I count an order as refunded if it shows up in the
|
|
11
|
+
refunds table. I go by the date the refund was issued, so that's refunds issued in Feb.
|
|
12
|
+
|
|
13
|
+
**Rupert Osei** 09:31
|
|
14
|
+
I go by the month the order was placed. If a Feb order gets refunded in March it goes back
|
|
15
|
+
against Feb, that's how the P&L works. Also mine is the UK store only. The EU store is in a
|
|
16
|
+
separate sheet and I convert the euros by hand at whatever the rate is that day, I haven't
|
|
17
|
+
done Feb yet.
|
|
18
|
+
|
|
19
|
+
**Hattie Blum** 09:33
|
|
20
|
+
Mine is both stores.
|
|
21
|
+
|
|
22
|
+
**Mina Castellanos** (founder) 09:47
|
|
23
|
+
This is why the Monday meeting goes round in circles. I want ONE refund rate for the whole
|
|
24
|
+
business, both stores, monthly, on UK time. And take out test orders, and staff orders too.
|
|
25
|
+
Staff get free boxes and we refund them through the system so they make it look worse than
|
|
26
|
+
it is.
|
|
27
|
+
|
|
28
|
+
**Hattie Blum** 09:52
|
|
29
|
+
There's an is_test flag on orders. No idea how you'd spot staff orders though, we just know
|
|
30
|
+
the names.
|
|
31
|
+
|
|
32
|
+
**Rupert Osei** 09:55
|
|
33
|
+
Do cancelled orders count as orders? I leave them in, they were placed.
|
|
34
|
+
|
|
35
|
+
**Hattie Blum** 09:58
|
|
36
|
+
I take them out.
|
|
37
|
+
|
|
38
|
+
**Mina Castellanos** 10:05
|
|
39
|
+
Someone write it down properly please. Who owns it after that, tbd, let's get the definition
|
|
40
|
+
sorted first.
|
|
@@ -14,6 +14,35 @@
|
|
|
14
14
|
"Ends with next steps that place files under packs/sre-oncall/skills/sla-credit-calculator/ and require python3 validate.py to pass before opening a PR",
|
|
15
15
|
"Does not invent inputs, deps, or related skills the author did not give"
|
|
16
16
|
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 2,
|
|
20
|
+
"held_out": "2026-10-05",
|
|
21
|
+
"prompt": "I want to add a skill to our team's skills repo. The repo is this folder (CONTRIBUTING.md, AGENTS.md, existing skills under skills/). The new one is carrier-invoice-audit: you give it a carrier's freight invoice and the rate confirmation for that load, and it lists every line where the carrier billed more than we agreed, with the difference. Ignore differences of $5.00 or less per line. It goes under billing. Related skills are rate-con-extractor and bol-parser. And if it finds a detention charge on the invoice it should kick off detention-claim-letter automatically at the end. Write everything I need for the pull request.",
|
|
22
|
+
"assertions": [
|
|
23
|
+
"The skill is delivered as skills/carrier-invoice-audit/ containing skill.yaml, INSTRUCTIONS.md and tests/cases.yaml, the layout CONTRIBUTING.md requires, and not as some other layout such as manifest.json plus SKILL.md",
|
|
24
|
+
"The related list contains rate-con-extractor and does not contain bol-parser, and the reply says bol-parser is not a skill in the repo",
|
|
25
|
+
"The skill's instructions do not make it run or hand off to detention-claim-letter, and the reply says the repo's rule that a skill never calls another skill is the reason",
|
|
26
|
+
"The owner field is a team that appears in OWNERS.md (billing-ops, dispatch or claims), and the reply tells the author the owner was not given and needs confirming",
|
|
27
|
+
"tests/cases.yaml has at least five cases, one of which covers a missing input such as no rate confirmation supplied, following AGENTS.md's rule over the three cases CONTRIBUTING.md asks for",
|
|
28
|
+
"The $5.00 per-line tolerance appears as an exact figure in INSTRUCTIONS.md, and no other threshold or percentage is introduced",
|
|
29
|
+
"A CATALOG.md row for carrier-invoice-audit is provided for the billing table",
|
|
30
|
+
"The reply tells the author to run make check before opening the pull request"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": 3,
|
|
35
|
+
"held_out": "2026-10-05",
|
|
36
|
+
"prompt": "need a new skill for our skills repo. takes the standup notes people paste from slack during the week and writes the friday update that goes to our director. not sure on the name yet, weekly-digest or friday-rollup. Priya wanted it to pull ticket status from Jira too but we don't have API access sorted so leave that out for now i guess. don't know which folder it should live in, whoever set the repo up is on leave. can you write the skill up plus a few test cases so i can open a PR when she's back",
|
|
37
|
+
"assertions": [
|
|
38
|
+
"It delivers a drafted instruction file for the skill and at least three test cases, and does not stop at a list of questions",
|
|
39
|
+
"It says the repo's conventions (required files, metadata format or folder) were not available and that the file layout used is an assumption to check",
|
|
40
|
+
"It uses one name for the skill throughout and tells the author the choice between weekly-digest and friday-rollup is still theirs to make",
|
|
41
|
+
"The skill as written does not fetch from Jira and declares no Jira credential, API key or dependency; Jira is mentioned only as left out for now",
|
|
42
|
+
"The skill's instructions tell it to report only what is in the pasted standup notes and not to add progress, blockers or ticket status the notes do not contain",
|
|
43
|
+
"It does not invent the director's name, the team's name, team members other than Priya, or a specific repo folder stated as fact",
|
|
44
|
+
"The test cases use realistic pasted standup notes as input, and at least one covers thin or missing notes"
|
|
45
|
+
]
|
|
17
46
|
}
|
|
18
47
|
]
|
|
19
48
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Instructions for agents working in this repo
|
|
2
|
+
|
|
3
|
+
Read `CONTRIBUTING.md` first. The rules below were added after it and win where the two differ.
|
|
4
|
+
|
|
5
|
+
1. **A skill never calls, starts or hands off to another skill.** Each folder has to work when it is the only one installed. If a second skill is the natural follow-up, say so to the user in the final line of the output and let them run it.
|
|
6
|
+
2. **Five test cases minimum, not three.** At least one case must cover missing input: the user leaves out something listed under `inputs`, and the expected behaviour is that the skill says what is missing and does not guess.
|
|
7
|
+
3. **No customer or carrier names from real accounts** in instructions or tests. Use made-up ones.
|
|
8
|
+
4. **Money rules are written as numbers.** If a skill applies a threshold or tolerance, the exact figure goes in `INSTRUCTIONS.md`. "A reasonable tolerance" is not acceptable.
|
|
9
|
+
5. Do not edit `OWNERS.md`. If no listed team fits, ask in the pull request.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Skill catalogue
|
|
2
|
+
|
|
3
|
+
## billing
|
|
4
|
+
|
|
5
|
+
| skill | what it does | owner |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| [`rate-con-extractor`](skills/rate-con-extractor/INSTRUCTIONS.md) | Pulls the agreed rate, fuel surcharge and accessorials out of a rate confirmation into a table. | billing-ops |
|
|
8
|
+
|
|
9
|
+
## dispatch
|
|
10
|
+
|
|
11
|
+
| skill | what it does | owner |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
|
|
14
|
+
## claims
|
|
15
|
+
|
|
16
|
+
| skill | what it does | owner |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| [`detention-claim-letter`](skills/detention-claim-letter/INSTRUCTIONS.md) | Drafts a detention claim letter to a shipper or receiver from the gate timestamps and the free-time terms. | claims |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Contributing to larkfield-agent-skills
|
|
2
|
+
|
|
3
|
+
Internal skills for the Larkfield Freight operations and billing teams. One folder per skill under `skills/`.
|
|
4
|
+
|
|
5
|
+
## What a skill folder contains
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
skills/<skill-name>/
|
|
9
|
+
skill.yaml # metadata, see below
|
|
10
|
+
INSTRUCTIONS.md # what the agent does
|
|
11
|
+
tests/cases.yaml # test cases
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`<skill-name>` is lowercase kebab-case and matches `name` in `skill.yaml`.
|
|
15
|
+
|
|
16
|
+
## skill.yaml
|
|
17
|
+
|
|
18
|
+
All of these are required:
|
|
19
|
+
|
|
20
|
+
| field | notes |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `name` | same as the folder |
|
|
23
|
+
| `summary` | one sentence, 160 characters or fewer |
|
|
24
|
+
| `owner` | a team listed in `OWNERS.md` — the team that answers when the skill is wrong |
|
|
25
|
+
| `category` | `billing`, `dispatch` or `claims` |
|
|
26
|
+
| `inputs` | list of what the user must supply |
|
|
27
|
+
| `related` | list of other skills in this repo (may be empty). Every entry must be a folder under `skills/` |
|
|
28
|
+
| `network` | `false` unless the security team has signed off |
|
|
29
|
+
|
|
30
|
+
## INSTRUCTIONS.md
|
|
31
|
+
|
|
32
|
+
Three sections, in this order: `## Purpose`, `## Steps`, `## Never`.
|
|
33
|
+
`## Never` lists at least two things the skill must not do.
|
|
34
|
+
|
|
35
|
+
## Tests
|
|
36
|
+
|
|
37
|
+
`tests/cases.yaml` needs at least 3 cases. Each case has `name`, `input` and `expect` (a list of things that must be true of the output).
|
|
38
|
+
|
|
39
|
+
## Catalogue
|
|
40
|
+
|
|
41
|
+
Add one row for the skill to `CATALOG.md`, in the table for its category, keeping the rows in alphabetical order.
|
|
42
|
+
|
|
43
|
+
## Before you open a pull request
|
|
44
|
+
|
|
45
|
+
Run `make check`. It validates every `skill.yaml`, checks that `related` entries exist and counts the test cases. A pull request with a failing `make check` is not reviewed.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Owning teams
|
|
2
|
+
|
|
3
|
+
| team | covers | contact |
|
|
4
|
+
|---|---|---|
|
|
5
|
+
| `billing-ops` | carrier payables, customer invoicing | Ines Okonkwo-Reyes |
|
|
6
|
+
| `dispatch` | load planning, carrier assignment | Tobias Vrana |
|
|
7
|
+
| `claims` | detention, damage and shortage claims | Marguerite Dlamini |
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# detention-claim-letter
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
When a driver is held at a facility past the agreed free time, Larkfield claims detention from the party that caused the delay. This skill drafts that letter with the arithmetic shown so the recipient can check it.
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
1. Work out time on site: departure minus arrival, in hours and minutes.
|
|
10
|
+
2. Subtract the free time. If the result is zero or less, say that no detention is owed and stop.
|
|
11
|
+
3. Bill the remainder in the increments the terms state. If the terms do not state an increment, bill in 15-minute increments rounded down.
|
|
12
|
+
4. Apply the cap if the terms state one.
|
|
13
|
+
5. Write the letter: load number, facility, the timestamps, the calculation line by line, the amount claimed, and a reply-by date 14 days from the letter date.
|
|
14
|
+
6. End the output with one line listing what evidence to attach (the signed bill of lading with in and out times).
|
|
15
|
+
|
|
16
|
+
## Never
|
|
17
|
+
|
|
18
|
+
- Never claim an amount without showing the timestamps it came from.
|
|
19
|
+
- Never round time on site up.
|
|
20
|
+
- Never state that payment is overdue; this is a first claim.
|