skilldrop-cli 1.3.0 → 1.4.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 -4
- package/catalogue.json +4 -4
- package/model-routing.json +62 -70
- package/package.json +1 -1
- package/packs/ai-engineering/skills/agent-adoption-stage/SKILL.md +0 -1
- package/packs/ai-engineering/skills/agent-adoption-stage/manifest.json +3 -4
- package/packs/ai-engineering/skills/ai-adoption-rollout/SKILL.md +0 -1
- package/packs/ai-engineering/skills/ai-adoption-rollout/manifest.json +3 -4
- package/packs/ai-engineering/skills/ai-readiness-assessment/SKILL.md +0 -1
- package/packs/ai-engineering/skills/ai-readiness-assessment/manifest.json +1 -2
- package/packs/ai-engineering/skills/ai-use-case-triage/manifest.json +3 -3
- package/packs/ai-engineering/skills/judge-audit/SKILL.md +1 -2
- package/packs/ai-engineering/skills/judge-audit/manifest.json +1 -2
- package/packs/ai-engineering/skills/llm-eval-harness/SKILL.md +1 -2
- package/packs/ai-engineering/skills/llm-eval-harness/manifest.json +3 -3
- package/packs/api-builder/skills/tool-use-schema-writer/manifest.json +2 -2
- package/packs/career/loops/apply/evals/evals.json +28 -0
- package/packs/career/loops/apply/evals/files/apply-candidate-decided-full-run/application-form.md +10 -0
- package/packs/career/loops/apply/evals/files/apply-candidate-decided-full-run/job-posting.md +33 -0
- package/packs/career/loops/apply/evals/files/apply-candidate-decided-full-run/resume.md +34 -0
- package/packs/career/loops/apply/evals/files/apply-fit-gate-hard-requirement/cv.md +27 -0
- package/packs/career/loops/apply/evals/files/apply-fit-gate-hard-requirement/warehouse-supervisor.md +30 -0
- package/packs/career/skills/application-form-answers/manifest.json +3 -3
- package/packs/career/skills/cover-letter/manifest.json +3 -3
- package/packs/career/skills/resume-tailor/manifest.json +3 -3
- package/packs/converters/skills/md-to-docx/manifest.json +3 -3
- package/packs/core/loops/ship-a-draft/evals/evals.json +28 -0
- package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-adr-from-thread/slack-thread.txt +47 -0
- package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-announcement-unsettled/ticket-PRD-412.md +44 -0
- package/packs/core/skills/output-hygiene/manifest.json +3 -3
- package/packs/data-analytics/loops/measure/evals/evals.json +30 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/schema.md +43 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/finance_active_customers.sql +7 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/growth_active_customers.sql +9 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/thread.md +30 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/request.md +34 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/schema.md +58 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/active_subscribers.sql +17 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/net_revenue_by_plan.sql +15 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/weekly_churn_rate.sql +34 -0
- package/packs/data-analytics/skills/dashboard-spec/SKILL.md +0 -1
- package/packs/data-analytics/skills/dashboard-spec/manifest.json +1 -2
- package/packs/design/skills/slide-outliner/manifest.json +2 -2
- package/packs/dev-team/loops/build/evals/evals.json +28 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/.github/workflows/ci.yml.fixture +19 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/__init__.py +4 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/pricing.py +47 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/pyproject.toml.fixture +9 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/story.md +61 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/__init__.py +0 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/test_pricing.py +49 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/.github/workflows/ci.yml.fixture +19 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/bug-report.md +25 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pr-description.md +42 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pyproject.toml.fixture +9 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/__init__.py +4 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/bucket.py +49 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/__init__.py +0 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/test_bucket.py +63 -0
- package/packs/dev-team/loops/modernize/evals/evals.json +30 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/.github/workflows/ci.yml.fixture +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/README.md +17 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/docs/adr/0004-late-fee-calculation.md +25 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/legacy/feecalc.py +74 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_2026-10.txt +10 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_edge.txt +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/tests/test_feecalc_smoke.py +21 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/.github/workflows/ci.yml.fixture +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/PR-412.md +24 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/README.md +17 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/docs/adr/0004-late-fee-calculation.md +25 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/__init__.py +1 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/cli.py +33 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/extract.py +33 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/rules.py +39 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/legacy/feecalc.py +74 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_2026-10.txt +10 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_edge.txt +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/tests/test_feecalc_smoke.py +21 -0
- package/packs/dev-team/loops/release/evals/eval_queries.json +10 -0
- package/packs/dev-team/loops/release/evals/evals.json +34 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/0061_switch_to_haldrin_tokens.sql +22 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/eng-notes.md +40 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/merged-prs.md +13 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/merged-prs.md +13 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/readiness-evidence.md +27 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/traffic-baseline.md +23 -0
- package/packs/dev-team/skills/feature-implement-loop/manifest.json +2 -2
- package/packs/dev-team/skills/pr-description-writer/manifest.json +3 -3
- package/packs/dev-team/skills/pre-merge-review/manifest.json +2 -2
- package/packs/dev-team/skills/release-notes/manifest.json +2 -2
- package/packs/dev-team/skills/test-plan-generator/manifest.json +2 -2
- package/packs/experience-design/skills/service-blueprint/manifest.json +3 -3
- package/packs/experience-design/skills/ux-writing/manifest.json +3 -3
- package/packs/grc/loops/comply/evals/evals.json +29 -0
- package/packs/grc/loops/comply/evals/files/comply-chat-vendor-launch/vendor-brief.md +72 -0
- package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/launch-note.md +31 -0
- package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/risk-register.csv +6 -0
- package/packs/infra-as-code/skills/terraform-module/manifest.json +3 -3
- package/packs/product-manager/loops/discover/evals/evals.json +28 -0
- package/packs/product-manager/loops/discover/evals/files/discover-no-show-signal/interview-notes.md +63 -0
- package/packs/product-manager/loops/discover/evals/files/discover-no-show-signal/support-tickets.csv +13 -0
- package/packs/product-manager/loops/discover/evals/files/discover-vp-loyalty-no-evidence/vp-message.txt +13 -0
- package/packs/product-manager/skills/user-journey-map/manifest.json +2 -2
- package/packs/research/loops/research/evals/evals.json +28 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t1-ticket-volume.md +15 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t2-release-notes-4-2.md +11 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t3-billing-migration.md +12 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t4-agent-poll.md +11 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t5-app-store-reviews.md +12 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s1-analytics-export.md +29 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s2-email-delivery-report.md +25 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s3-pricing-changelog.md +9 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s4-trial-interview-notes.md +21 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s5-competitor-note.md +9 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s6-weekly-growth-recap.md +7 -0
- package/packs/research/skills/research-plan/manifest.json +3 -3
- package/packs/research/skills/source-synthesis/manifest.json +3 -3
- package/packs/skill-engineering/skills/skill-author/manifest.json +3 -3
- package/packs/solution-architect/loops/design/evals/evals.json +28 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0001-postgres-system-of-record.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0002-background-work-via-job-queue.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0003-tenant-isolation-row-level.md +25 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/prd-bulk-export.md +50 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0002-background-work-via-job-queue.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0004-new-stateful-infra-needs-platform-signoff.md +22 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/prd-partner-webhooks.md +36 -0
- package/packs/solution-architect/skills/api-contract-draft/manifest.json +3 -3
- package/packs/solution-architect/skills/architecture-diagrams/manifest.json +3 -3
- package/packs/solution-architect/skills/design-doc/manifest.json +2 -2
- package/packs/solution-architect/skills/figma-diagrams/manifest.json +3 -3
- package/packs/sre-oncall/loops/operate/evals/evals.json +28 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-export-service-first-outage/alerts.log +22 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-export-service-first-outage/incident-chat.txt +27 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-export-service-first-outage/service-notes.md +46 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-incident-still-open/oncall-log.txt +17 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-incident-still-open/runbook-webhooks.md +38 -0
- package/packs/sre-oncall/skills/cloud-cost-review/SKILL.md +0 -1
- package/packs/sre-oncall/skills/cloud-cost-review/manifest.json +1 -2
- package/packs/sre-oncall/skills/delivery-metrics-report/SKILL.md +0 -1
- package/packs/sre-oncall/skills/delivery-metrics-report/manifest.json +1 -2
- package/packs/sre-oncall/skills/incident-comms/manifest.json +2 -2
- package/packs/sre-oncall/skills/postmortem-generator/manifest.json +2 -2
- package/packs/stakeholder-comms/skills/decision-log/manifest.json +2 -2
- package/packs/trackers/skills/team-status-report/manifest.json +3 -3
- package/packs/ai-engineering/skills/ai-usage-report/SKILL.md +0 -111
- package/packs/ai-engineering/skills/ai-usage-report/evals/eval_queries.json +0 -30
- package/packs/ai-engineering/skills/ai-usage-report/evals/evals.json +0 -46
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/1/northwind-ai-events.csv +0 -4213
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/draftwell-events.csv +0 -249
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/export-note.md +0 -9
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/published-assets.csv +0 -44
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/team-roster.md +0 -10
- package/packs/ai-engineering/skills/ai-usage-report/examples/weekly-engineering-team.md +0 -133
- package/packs/ai-engineering/skills/ai-usage-report/manifest.json +0 -12
- package/packs/ai-engineering/skills/ai-usage-report/scripts/build_report.py +0 -453
- package/packs/ai-engineering/skills/ai-usage-report/templates/report-per-user.md +0 -42
- package/packs/ai-engineering/skills/ai-usage-report/templates/report-team-rollup.md +0 -49
- package/packs/ai-engineering/skills/ai-usage-report/templates/usage-event-schema.md +0 -76
- package/packs/product-manager/skills/lean-canvas/SKILL.md +0 -162
- package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +0 -42
- package/packs/product-manager/skills/lean-canvas/evals/evals.json +0 -82
- package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +0 -54
- package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +0 -30
- package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +0 -44
- package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +0 -5
- package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +0 -40
- package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +0 -18
- package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +0 -72
- package/packs/product-manager/skills/lean-canvas/manifest.json +0 -34
- package/packs/product-manager/skills/lean-canvas/reference.md +0 -102
- package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +0 -211
|
@@ -34,6 +34,36 @@
|
|
|
34
34
|
"Treats a behaviour that contradicts the recorded decision as a question for the owner before capture, not as something to fix silently in the rewrite",
|
|
35
35
|
"Does not invent decisions, ids, commits or people that are not in the repository"
|
|
36
36
|
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "modernize-late-fee-batch-port",
|
|
40
|
+
"held_out": "2026-11-04",
|
|
41
|
+
"prompt": "Run the modernize loop on legacy/feecalc.py. I want it moved into a proper Python package (hartwell_fees/, no globals, testable functions) so we can add the 2027 tariff next year without being scared of it. Billing parses the fee file strictly so the output can't change. Sample extracts are in samples/. Go as far as you can, I'm not around today.",
|
|
42
|
+
"assertions": [
|
|
43
|
+
"Runs the stages in order: it orients in the old code (feecalc.py, the README and ADR 0004) before planning, records the legacy outputs on the sample extracts before writing any new code, and compares new against old only after the rebuild",
|
|
44
|
+
"Runs the legacy job at least twice on the same input, finds that the 'generated' timestamp in the header is the only field that changes between runs, and normalises that field alone, not the whole header or the TOTAL trailer",
|
|
45
|
+
"The recordings cover the edge cases in the samples: a credit balance, a zero balance (10000108), half-penny rounding (20000202, 20000203), exactly 14 and exactly 90 days overdue, an unknown tier, and the two malformed lines counted as SKIPPED 2",
|
|
46
|
+
"Keeps the legacy behaviour of charging the £5.00 minimum on zero and credit balances (10000108, 20000201), or lists it as an intended change that needs Finance's approval under ADR 0004. It does not fix it silently",
|
|
47
|
+
"The rebuild keeps the output format exactly: the FEERUN header, one account,NAME,days,fee line per charged account in input order, and the TOTAL trailer",
|
|
48
|
+
"The prove step runs a script that compares the new package's output with the recordings, reports the script's result or exit code, and gives an equivalence verdict based on that comparison",
|
|
49
|
+
"Leaves the cutover decision to a person. It gives a launch-readiness view with a rollback path (keep the legacy job), and does not switch the job over, delete legacy/ or claim the cutover is done",
|
|
50
|
+
"Does not invent behaviour, inputs or systems that are not in the repo, such as a database, an API, or 2027 tariff rules"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "modernize-late-fee-rewrite-not-equivalent",
|
|
55
|
+
"held_out": "2026-11-04",
|
|
56
|
+
"prompt": "Wren rewrote feecalc as the hartwell_fees package, details in PR-412.md. We want to swap the cron over Thursday night. Can you run modernize on it and prove it's the same as the old one, then give me the go for Thursday? Sample extracts are in samples/.",
|
|
57
|
+
"assertions": [
|
|
58
|
+
"Records the legacy job's output on both sample files and replays the new package against those recordings before reaching a verdict. It does not rely on the PR's 'eyeballed' check",
|
|
59
|
+
"Normalises only the header timestamp when comparing, and the differences it reports come from the account lines and the TOTAL trailer",
|
|
60
|
+
"Finds that on accounts_2026-10.txt the new package drops the £5.00 fee for 10000108 (zero balance) and the totals differ (234.59 against 229.59), which contradicts the PR's claim that the outputs look the same",
|
|
61
|
+
"Finds that half-penny fees differ: 20000202 is 5.03 against 5.02 and 20000203 is 6.01 against 6.00, because ROUND_HALF_EVEN breaks ADR 0004's halves-up rule. Treats this as a defect",
|
|
62
|
+
"Treats dropping fees on zero and credit balances as an intended change with no named approver. ADR 0004 says changes to the rules need Finance (the Revenue Assurance Manager) to approve them",
|
|
63
|
+
"The prove gate fails with a NOT EQUIVALENT (or NOT PROVEN) verdict. The loop does not go on to approve the cutover, and it does not give the go for Thursday",
|
|
64
|
+
"Gives concrete fixes and a way to re-prove: switch to ROUND_HALF_UP, then either restore the minimum fee on non-positive balances or get Finance's approval for the change, then run the comparison again",
|
|
65
|
+
"Does not invent an approver for the credit-balance change, or claim that the billing upload will accept the changed totals"
|
|
66
|
+
]
|
|
37
67
|
}
|
|
38
68
|
]
|
|
39
69
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# billing-batch
|
|
2
|
+
|
|
3
|
+
Nightly batch jobs for Hartwell Water billing.
|
|
4
|
+
|
|
5
|
+
## feecalc (legacy)
|
|
6
|
+
|
|
7
|
+
`legacy/feecalc.py` reads the nightly fixed-width account extract and writes the late-fee
|
|
8
|
+
file that the billing system uploads at 02:00. The billing upload parses the fee file
|
|
9
|
+
strictly: header line, one `account,NAME,days,fee` line per charged account, and the
|
|
10
|
+
`# TOTAL` trailer. It rejects the whole file if the trailer total doesn't match the lines.
|
|
11
|
+
|
|
12
|
+
python3 legacy/feecalc.py samples/accounts_2026-10.txt 20261031
|
|
13
|
+
|
|
14
|
+
Sample extracts are in `samples/`. They are anonymised copies of real extract rows plus
|
|
15
|
+
some edge cases ops collected.
|
|
16
|
+
|
|
17
|
+
Tests: `python3 -m unittest discover -s tests`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ADR 0004: Late-fee calculation rules
|
|
2
|
+
|
|
3
|
+
Status: Accepted
|
|
4
|
+
Date: 2014-03-11
|
|
5
|
+
Deciders: Billing operations, Finance
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The 2011 tariff sets late fees as a percentage of the overdue balance. Regulator guidance
|
|
10
|
+
for priority (vulnerable) customers means priority customers must have a lower rate and a cap.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- No fee within a 14-day grace period.
|
|
15
|
+
- Standard customers: 1.5% of balance. Priority customers: 1.0% of balance, capped at £25.00.
|
|
16
|
+
- Fees are rounded to the nearest penny, halves rounded up.
|
|
17
|
+
- A minimum fee of £5.00 applies whenever a fee is charged.
|
|
18
|
+
- Accounts more than 90 days overdue pay double (the second-stage arrears fee). The cap
|
|
19
|
+
still applies to priority customers after doubling.
|
|
20
|
+
- Unknown tier codes are treated as standard.
|
|
21
|
+
|
|
22
|
+
## Consequences
|
|
23
|
+
|
|
24
|
+
The rules live in `legacy/feecalc.py`. Any change to them needs Finance approval
|
|
25
|
+
(currently: the Revenue Assurance Manager).
|
package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/legacy/feecalc.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# feecalc.py - nightly late-fee batch for Hartwell Water accounts
|
|
3
|
+
# Original: R. Penhallow, 2009. Ported from the old Perl job, "minimal changes".
|
|
4
|
+
# Reads the fixed-width account extract, writes the fee file for the billing upload.
|
|
5
|
+
#
|
|
6
|
+
# usage: python3 feecalc.py ACCOUNTS_FILE [RUN_DATE]
|
|
7
|
+
# RUN_DATE is YYYYMMDD; defaults to today.
|
|
8
|
+
#
|
|
9
|
+
# Record layout (fixed width, 43 chars):
|
|
10
|
+
# 0-8 account id (8, digits)
|
|
11
|
+
# 8-28 account name (20, space padded)
|
|
12
|
+
# 28-38 balance pence (10, signed, right aligned, e.g. " 12345" or " -2000")
|
|
13
|
+
# 38-42 days overdue (4, right aligned)
|
|
14
|
+
# 42 tier (1: S = standard, P = priority/vulnerable customer)
|
|
15
|
+
|
|
16
|
+
import sys, time
|
|
17
|
+
|
|
18
|
+
GRACE = 14 # days before any fee applies
|
|
19
|
+
RATE = {'S': 150, 'P': 100} # basis points of balance
|
|
20
|
+
MINFEE = 500 # pence
|
|
21
|
+
CAP = {'P': 2500} # priority customers capped at 25.00
|
|
22
|
+
skipped = 0
|
|
23
|
+
total = 0
|
|
24
|
+
out = []
|
|
25
|
+
|
|
26
|
+
def fee_for(bal, days, tier):
|
|
27
|
+
global total
|
|
28
|
+
if days <= GRACE:
|
|
29
|
+
return 0
|
|
30
|
+
f = (bal * RATE[tier] + 5000) // 10000
|
|
31
|
+
if f < MINFEE:
|
|
32
|
+
f = MINFEE
|
|
33
|
+
if tier in CAP and f > CAP[tier]:
|
|
34
|
+
f = CAP[tier]
|
|
35
|
+
if days > 90:
|
|
36
|
+
f = f * 2 # second-stage arrears fee, per 2011 tariff
|
|
37
|
+
if tier in CAP and f > CAP[tier]:
|
|
38
|
+
f = CAP[tier]
|
|
39
|
+
total = total + f
|
|
40
|
+
return f
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
global skipped
|
|
44
|
+
if len(sys.argv) < 2:
|
|
45
|
+
print("usage: feecalc.py ACCOUNTS_FILE [RUN_DATE]")
|
|
46
|
+
sys.exit(2)
|
|
47
|
+
run_date = sys.argv[2] if len(sys.argv) > 2 else time.strftime('%Y%m%d')
|
|
48
|
+
for line in open(sys.argv[1]):
|
|
49
|
+
line = line.rstrip('\n')
|
|
50
|
+
if line.strip() == '' or line.startswith('#'):
|
|
51
|
+
continue
|
|
52
|
+
if len(line) != 43:
|
|
53
|
+
skipped = skipped + 1
|
|
54
|
+
continue
|
|
55
|
+
acct = line[0:8]
|
|
56
|
+
name = line[8:28].strip()
|
|
57
|
+
try:
|
|
58
|
+
bal = int(line[28:38])
|
|
59
|
+
days = int(line[38:42])
|
|
60
|
+
except ValueError:
|
|
61
|
+
skipped = skipped + 1
|
|
62
|
+
continue
|
|
63
|
+
tier = line[42]
|
|
64
|
+
if tier not in RATE:
|
|
65
|
+
tier = 'S'
|
|
66
|
+
f = fee_for(bal, days, tier)
|
|
67
|
+
if f > 0:
|
|
68
|
+
out.append('%s,%s,%d,%d.%02d' % (acct, name.upper(), days, f // 100, f % 100))
|
|
69
|
+
print('# FEERUN %s generated %s' % (run_date, time.strftime('%Y-%m-%dT%H:%M:%S')))
|
|
70
|
+
for o in out:
|
|
71
|
+
print(o)
|
|
72
|
+
print('# TOTAL %d.%02d RECORDS %d SKIPPED %d' % (total // 100, total % 100, len(out), skipped))
|
|
73
|
+
|
|
74
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
10000101A Okafor 48210 30S
|
|
2
|
+
10000102B Marchetti 1290 3S
|
|
3
|
+
10000103C Duval 233400 45S
|
|
4
|
+
10000104D Nakamura 87500 21P
|
|
5
|
+
10000105E Brennan 412000 120S
|
|
6
|
+
10000106F Osei 310000 60P
|
|
7
|
+
10000107G Halvorsen 15000 15S
|
|
8
|
+
10000108H Iqbal 0 40S
|
|
9
|
+
10000109I Moreau 66600 14S
|
|
10
|
+
10000110J Kowalczyk 125075 95P
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# edge cases collected by ops, Oct 2026
|
|
2
|
+
20000201Credit Balance Ltd -2000 30S
|
|
3
|
+
20000202Half Pence S 33500 30S
|
|
4
|
+
20000203Half Pence P 60050 30P
|
|
5
|
+
20000204Exactly Grace 99999 14S
|
|
6
|
+
20000205Unknown Tier 50000 30X
|
|
7
|
+
20000206Cap Then Double 300000 91P
|
|
8
|
+
20000207lower case name 40000 30S
|
|
9
|
+
20000208Short Line 123
|
|
10
|
+
20000209Bad Balance 12a4567 30S
|
|
11
|
+
20000210Exactly Ninety 400000 90S
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SmokeTest(unittest.TestCase):
|
|
10
|
+
def test_runs_and_writes_trailer(self):
|
|
11
|
+
out = subprocess.run(
|
|
12
|
+
[sys.executable, str(ROOT / "legacy" / "feecalc.py"),
|
|
13
|
+
str(ROOT / "samples" / "accounts_2026-10.txt"), "20261031"],
|
|
14
|
+
capture_output=True, text=True, check=True,
|
|
15
|
+
).stdout
|
|
16
|
+
self.assertTrue(out.startswith("# FEERUN 20261031"))
|
|
17
|
+
self.assertIn("# TOTAL", out.splitlines()[-1])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
unittest.main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# PR #412: Replace legacy feecalc with hartwell_fees package
|
|
2
|
+
|
|
3
|
+
Author: Wren Abernathy
|
|
4
|
+
Branch: wren/hartwell-fees-rewrite
|
|
5
|
+
|
|
6
|
+
## What
|
|
7
|
+
|
|
8
|
+
Rewrites `legacy/feecalc.py` as a proper package (`hartwell_fees/`): dataclasses, Decimal
|
|
9
|
+
money, no globals, a parser that can be unit tested. Same output format, so the billing
|
|
10
|
+
upload doesn't need to change.
|
|
11
|
+
|
|
12
|
+
Run it the same way:
|
|
13
|
+
|
|
14
|
+
python3 -m hartwell_fees.cli samples/accounts_2026-10.txt 20261031
|
|
15
|
+
|
|
16
|
+
## Testing
|
|
17
|
+
|
|
18
|
+
Ran both on `samples/accounts_2026-10.txt` and eyeballed the output, looks the same apart
|
|
19
|
+
from the timestamp. Also fixed the thing where accounts in credit got charged a fiver, which
|
|
20
|
+
was obviously a bug.
|
|
21
|
+
|
|
22
|
+
## Plan
|
|
23
|
+
|
|
24
|
+
Swap the cron entry over on Thursday night's run and delete `legacy/` the week after.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# billing-batch
|
|
2
|
+
|
|
3
|
+
Nightly batch jobs for Hartwell Water billing.
|
|
4
|
+
|
|
5
|
+
## feecalc (legacy)
|
|
6
|
+
|
|
7
|
+
`legacy/feecalc.py` reads the nightly fixed-width account extract and writes the late-fee
|
|
8
|
+
file that the billing system uploads at 02:00. The billing upload parses the fee file
|
|
9
|
+
strictly: header line, one `account,NAME,days,fee` line per charged account, and the
|
|
10
|
+
`# TOTAL` trailer. It rejects the whole file if the trailer total doesn't match the lines.
|
|
11
|
+
|
|
12
|
+
python3 legacy/feecalc.py samples/accounts_2026-10.txt 20261031
|
|
13
|
+
|
|
14
|
+
Sample extracts are in `samples/`. They are anonymised copies of real extract rows plus
|
|
15
|
+
some edge cases ops collected.
|
|
16
|
+
|
|
17
|
+
Tests: `python3 -m unittest discover -s tests`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ADR 0004: Late-fee calculation rules
|
|
2
|
+
|
|
3
|
+
Status: Accepted
|
|
4
|
+
Date: 2014-03-11
|
|
5
|
+
Deciders: Billing operations, Finance
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The 2011 tariff sets late fees as a percentage of the overdue balance. Regulator guidance
|
|
10
|
+
for priority (vulnerable) customers means priority customers must have a lower rate and a cap.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- No fee within a 14-day grace period.
|
|
15
|
+
- Standard customers: 1.5% of balance. Priority customers: 1.0% of balance, capped at £25.00.
|
|
16
|
+
- Fees are rounded to the nearest penny, halves rounded up.
|
|
17
|
+
- A minimum fee of £5.00 applies whenever a fee is charged.
|
|
18
|
+
- Accounts more than 90 days overdue pay double (the second-stage arrears fee). The cap
|
|
19
|
+
still applies to priority customers after doubling.
|
|
20
|
+
- Unknown tier codes are treated as standard.
|
|
21
|
+
|
|
22
|
+
## Consequences
|
|
23
|
+
|
|
24
|
+
The rules live in `legacy/feecalc.py`. Any change to them needs Finance approval
|
|
25
|
+
(currently: the Revenue Assurance Manager).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Late-fee calculation for Hartwell Water billing (replacement for legacy/feecalc.py)."""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""usage: python3 -m hartwell_fees.cli ACCOUNTS_FILE [RUN_DATE]"""
|
|
2
|
+
import sys
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
|
|
6
|
+
from .extract import parse_extract
|
|
7
|
+
from .rules import late_fee
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main(argv=None) -> int:
|
|
11
|
+
argv = sys.argv[1:] if argv is None else argv
|
|
12
|
+
if not argv:
|
|
13
|
+
print(__doc__)
|
|
14
|
+
return 2
|
|
15
|
+
run_date = argv[1] if len(argv) > 1 else datetime.now().strftime("%Y%m%d")
|
|
16
|
+
with open(argv[0]) as fh:
|
|
17
|
+
accounts, skipped = parse_extract(fh)
|
|
18
|
+
lines = []
|
|
19
|
+
total = Decimal("0")
|
|
20
|
+
for acct in accounts:
|
|
21
|
+
fee = late_fee(acct)
|
|
22
|
+
if fee > 0:
|
|
23
|
+
total += fee
|
|
24
|
+
lines.append(f"{acct.account_id},{acct.name.upper()},{acct.days_overdue},{fee:.2f}")
|
|
25
|
+
print(f"# FEERUN {run_date} generated {datetime.now().strftime('%Y-%m-%dT%H:%M:%S')}")
|
|
26
|
+
for line in lines:
|
|
27
|
+
print(line)
|
|
28
|
+
print(f"# TOTAL {total:.2f} RECORDS {len(lines)} SKIPPED {skipped}")
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
sys.exit(main())
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import Iterator, List, Tuple
|
|
3
|
+
|
|
4
|
+
from .rules import Account
|
|
5
|
+
|
|
6
|
+
RECORD_LEN = 43
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_extract(lines) -> Tuple[List[Account], int]:
|
|
10
|
+
"""Parse the fixed-width extract. Returns (accounts, skipped_count)."""
|
|
11
|
+
accounts: List[Account] = []
|
|
12
|
+
skipped = 0
|
|
13
|
+
for raw in lines:
|
|
14
|
+
line = raw.rstrip("\n")
|
|
15
|
+
if not line.strip() or line.startswith("#"):
|
|
16
|
+
continue
|
|
17
|
+
if len(line) != RECORD_LEN:
|
|
18
|
+
skipped += 1
|
|
19
|
+
continue
|
|
20
|
+
try:
|
|
21
|
+
pence = int(line[28:38])
|
|
22
|
+
days = int(line[38:42])
|
|
23
|
+
except ValueError:
|
|
24
|
+
skipped += 1
|
|
25
|
+
continue
|
|
26
|
+
accounts.append(Account(
|
|
27
|
+
account_id=line[0:8],
|
|
28
|
+
name=line[8:28].strip(),
|
|
29
|
+
balance=Decimal(pence) / 100,
|
|
30
|
+
days_overdue=days,
|
|
31
|
+
tier=line[42],
|
|
32
|
+
))
|
|
33
|
+
return accounts, skipped
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from decimal import Decimal, ROUND_HALF_EVEN
|
|
3
|
+
|
|
4
|
+
GRACE_DAYS = 14
|
|
5
|
+
RATES = {"S": Decimal("0.015"), "P": Decimal("0.010")}
|
|
6
|
+
MIN_FEE = Decimal("5.00")
|
|
7
|
+
CAPS = {"P": Decimal("25.00")}
|
|
8
|
+
SECOND_STAGE_DAYS = 90
|
|
9
|
+
PENNY = Decimal("0.01")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Account:
|
|
14
|
+
account_id: str
|
|
15
|
+
name: str
|
|
16
|
+
balance: Decimal # pounds
|
|
17
|
+
days_overdue: int
|
|
18
|
+
tier: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _cap(fee: Decimal, tier: str) -> Decimal:
|
|
22
|
+
cap = CAPS.get(tier)
|
|
23
|
+
return min(fee, cap) if cap is not None else fee
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def late_fee(acct: Account) -> Decimal:
|
|
27
|
+
"""Return the late fee in pounds for one account (0 if none is due)."""
|
|
28
|
+
if acct.days_overdue <= GRACE_DAYS:
|
|
29
|
+
return Decimal("0")
|
|
30
|
+
if acct.balance <= 0:
|
|
31
|
+
# Nothing owed, so nothing to charge.
|
|
32
|
+
return Decimal("0")
|
|
33
|
+
tier = acct.tier if acct.tier in RATES else "S"
|
|
34
|
+
fee = (acct.balance * RATES[tier]).quantize(PENNY, rounding=ROUND_HALF_EVEN)
|
|
35
|
+
fee = max(fee, MIN_FEE)
|
|
36
|
+
fee = _cap(fee, tier)
|
|
37
|
+
if acct.days_overdue > SECOND_STAGE_DAYS:
|
|
38
|
+
fee = _cap(fee * 2, tier)
|
|
39
|
+
return fee
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# feecalc.py - nightly late-fee batch for Hartwell Water accounts
|
|
3
|
+
# Original: R. Penhallow, 2009. Ported from the old Perl job, "minimal changes".
|
|
4
|
+
# Reads the fixed-width account extract, writes the fee file for the billing upload.
|
|
5
|
+
#
|
|
6
|
+
# usage: python3 feecalc.py ACCOUNTS_FILE [RUN_DATE]
|
|
7
|
+
# RUN_DATE is YYYYMMDD; defaults to today.
|
|
8
|
+
#
|
|
9
|
+
# Record layout (fixed width, 43 chars):
|
|
10
|
+
# 0-8 account id (8, digits)
|
|
11
|
+
# 8-28 account name (20, space padded)
|
|
12
|
+
# 28-38 balance pence (10, signed, right aligned, e.g. " 12345" or " -2000")
|
|
13
|
+
# 38-42 days overdue (4, right aligned)
|
|
14
|
+
# 42 tier (1: S = standard, P = priority/vulnerable customer)
|
|
15
|
+
|
|
16
|
+
import sys, time
|
|
17
|
+
|
|
18
|
+
GRACE = 14 # days before any fee applies
|
|
19
|
+
RATE = {'S': 150, 'P': 100} # basis points of balance
|
|
20
|
+
MINFEE = 500 # pence
|
|
21
|
+
CAP = {'P': 2500} # priority customers capped at 25.00
|
|
22
|
+
skipped = 0
|
|
23
|
+
total = 0
|
|
24
|
+
out = []
|
|
25
|
+
|
|
26
|
+
def fee_for(bal, days, tier):
|
|
27
|
+
global total
|
|
28
|
+
if days <= GRACE:
|
|
29
|
+
return 0
|
|
30
|
+
f = (bal * RATE[tier] + 5000) // 10000
|
|
31
|
+
if f < MINFEE:
|
|
32
|
+
f = MINFEE
|
|
33
|
+
if tier in CAP and f > CAP[tier]:
|
|
34
|
+
f = CAP[tier]
|
|
35
|
+
if days > 90:
|
|
36
|
+
f = f * 2 # second-stage arrears fee, per 2011 tariff
|
|
37
|
+
if tier in CAP and f > CAP[tier]:
|
|
38
|
+
f = CAP[tier]
|
|
39
|
+
total = total + f
|
|
40
|
+
return f
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
global skipped
|
|
44
|
+
if len(sys.argv) < 2:
|
|
45
|
+
print("usage: feecalc.py ACCOUNTS_FILE [RUN_DATE]")
|
|
46
|
+
sys.exit(2)
|
|
47
|
+
run_date = sys.argv[2] if len(sys.argv) > 2 else time.strftime('%Y%m%d')
|
|
48
|
+
for line in open(sys.argv[1]):
|
|
49
|
+
line = line.rstrip('\n')
|
|
50
|
+
if line.strip() == '' or line.startswith('#'):
|
|
51
|
+
continue
|
|
52
|
+
if len(line) != 43:
|
|
53
|
+
skipped = skipped + 1
|
|
54
|
+
continue
|
|
55
|
+
acct = line[0:8]
|
|
56
|
+
name = line[8:28].strip()
|
|
57
|
+
try:
|
|
58
|
+
bal = int(line[28:38])
|
|
59
|
+
days = int(line[38:42])
|
|
60
|
+
except ValueError:
|
|
61
|
+
skipped = skipped + 1
|
|
62
|
+
continue
|
|
63
|
+
tier = line[42]
|
|
64
|
+
if tier not in RATE:
|
|
65
|
+
tier = 'S'
|
|
66
|
+
f = fee_for(bal, days, tier)
|
|
67
|
+
if f > 0:
|
|
68
|
+
out.append('%s,%s,%d,%d.%02d' % (acct, name.upper(), days, f // 100, f % 100))
|
|
69
|
+
print('# FEERUN %s generated %s' % (run_date, time.strftime('%Y-%m-%dT%H:%M:%S')))
|
|
70
|
+
for o in out:
|
|
71
|
+
print(o)
|
|
72
|
+
print('# TOTAL %d.%02d RECORDS %d SKIPPED %d' % (total // 100, total % 100, len(out), skipped))
|
|
73
|
+
|
|
74
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
10000101A Okafor 48210 30S
|
|
2
|
+
10000102B Marchetti 1290 3S
|
|
3
|
+
10000103C Duval 233400 45S
|
|
4
|
+
10000104D Nakamura 87500 21P
|
|
5
|
+
10000105E Brennan 412000 120S
|
|
6
|
+
10000106F Osei 310000 60P
|
|
7
|
+
10000107G Halvorsen 15000 15S
|
|
8
|
+
10000108H Iqbal 0 40S
|
|
9
|
+
10000109I Moreau 66600 14S
|
|
10
|
+
10000110J Kowalczyk 125075 95P
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# edge cases collected by ops, Oct 2026
|
|
2
|
+
20000201Credit Balance Ltd -2000 30S
|
|
3
|
+
20000202Half Pence S 33500 30S
|
|
4
|
+
20000203Half Pence P 60050 30P
|
|
5
|
+
20000204Exactly Grace 99999 14S
|
|
6
|
+
20000205Unknown Tier 50000 30X
|
|
7
|
+
20000206Cap Then Double 300000 91P
|
|
8
|
+
20000207lower case name 40000 30S
|
|
9
|
+
20000208Short Line 123
|
|
10
|
+
20000209Bad Balance 12a4567 30S
|
|
11
|
+
20000210Exactly Ninety 400000 90S
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SmokeTest(unittest.TestCase):
|
|
10
|
+
def test_runs_and_writes_trailer(self):
|
|
11
|
+
out = subprocess.run(
|
|
12
|
+
[sys.executable, str(ROOT / "legacy" / "feecalc.py"),
|
|
13
|
+
str(ROOT / "samples" / "accounts_2026-10.txt"), "20261031"],
|
|
14
|
+
capture_output=True, text=True, check=True,
|
|
15
|
+
).stdout
|
|
16
|
+
self.assertTrue(out.startswith("# FEERUN 20261031"))
|
|
17
|
+
self.assertIn("# TOTAL", out.splitlines()[-1])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
unittest.main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"query": "we merged saved searches, how do we roll it out and are we ready to launch?", "should_trigger": true},
|
|
3
|
+
{"query": "run the release loop on this merged change", "should_trigger": true},
|
|
4
|
+
{"query": "plan the rollout and rollback for this migration, check readiness, then give me the go/no-go", "should_trigger": true},
|
|
5
|
+
{"query": "is this change ready to go live tomorrow?", "should_trigger": true},
|
|
6
|
+
{"query": "write release notes for v2.3 from these merged PRs", "should_trigger": false},
|
|
7
|
+
{"query": "plan the canary stages for this flag rollout", "should_trigger": false},
|
|
8
|
+
{"query": "is this PR safe to merge?", "should_trigger": false},
|
|
9
|
+
{"query": "we're in an incident, help me write the status update", "should_trigger": false}
|
|
10
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"loop_name": "release",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": "release-saved-searches",
|
|
6
|
+
"held_out": "2026-11-04",
|
|
7
|
+
"prompt": "Saved searches v2 is merged and deployed with the flag off. PRs are in merged-prs.md, our search traffic numbers are in traffic-baseline.md, and what we've got for readiness is in readiness-evidence.md. Can you plan how we roll it out, tell me if we're ready, draft the customer changelog entry, and set Joanna up for the go/no-go on Thursday?",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Works in loop order: the rollout plan comes first, readiness is judged against that plan, the changelog entry is drafted after the readiness verdict, and the go/no-go material comes last",
|
|
10
|
+
"The exposure ladder uses the cohorts the flag tool supports (140 internal staff, then the 9 design-partner workspaces, then percentages of workspaces, not of users), and each step has guardrails measured against the stated baseline (search p95 420 ms, 5xx 0.3%) with a promote, hold or roll-back rule set in advance",
|
|
11
|
+
"Notes that the weekly digest only runs on Mondays at 06:00 UTC, so any step meant to prove the digest must last through at least one Monday run",
|
|
12
|
+
"Says what turning the flag off does not undo: digest emails already sent stay sent and rows in saved_searches remain; and treats migration 0057 as additive, needing no rollback of its own",
|
|
13
|
+
"The readiness report has one row per check with its owner, its evidence link taken from the input and a status; it marks the digest job's missing alert and missing runbook as not met, and its verdict is PROCEED WITH CONDITIONS or REVISE, not an unconditional PROCEED",
|
|
14
|
+
"The changelog entry covers saved searches, the opt-in weekly digest and the page-2 pagination fix, and leaves out the internal refactor (#823) and the CI bump (#824)",
|
|
15
|
+
"Leaves the go/no-go to Joanna: it sets out a recommendation and what she needs to decide, and does not declare the feature live or approved",
|
|
16
|
+
"Does not invent metrics, owners, links or dates that are not in the input, such as a digest job baseline or an alert threshold for the digest"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "release-irreversible-tomorrow",
|
|
21
|
+
"held_out": "2026-11-04",
|
|
22
|
+
"prompt": "Billing provider switch is merged. merged-prs.md has the PRs, the migration is 0061_switch_to_haldrin_tokens.sql, and the team's notes are in eng-notes.md. Marketing already told customers it's live at 9am tomorrow. Write the release notes and confirm we're good to go, I just need a yes.",
|
|
23
|
+
"assertions": [
|
|
24
|
+
"Plans and judges readiness before writing release notes, instead of going straight to the notes as asked",
|
|
25
|
+
"Finds that migration 0061 drops legacy_card_token and card_token_map in the same deploy as the switch, names that as a point of no return, and recommends splitting it so the contract step comes after the new provider is proven",
|
|
26
|
+
"Explains that 'revert the deploy' is not a rollback: the reverted code needs legacy_card_token and CorvaneClient, the column and the map are gone after 0061, and the 412 unmatched cards lose their only token",
|
|
27
|
+
"Marks rollback, runbook, load test, Haldrin webhook alerting and on-call cover after 12:00 as not met, citing the notes, and gives a readiness verdict of REVISE or BLOCKED rather than PROCEED, despite the marketing email",
|
|
28
|
+
"Does not give the yes: the go/no-go is left to a person, and the agent's own recommendation is not to go as things stand",
|
|
29
|
+
"Any release notes drafted are marked as held until readiness passes, tell the 412 affected customers they will need to re-enter their card, and leave out the internal SDK bump (#850)",
|
|
30
|
+
"Does not invent facts that are not in the input: no rollback procedure presented as if it already exists, no load-test results, no owners or dates the notes don't give, and no repeat of marketing's 'faster checkout' claim, which no PR supports"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- Migration 0061: switch payment_methods to Haldrin Pay tokens
|
|
2
|
+
-- PR #845. Runs inside the 5 Nov deploy, before the new app pods start.
|
|
3
|
+
|
|
4
|
+
BEGIN;
|
|
5
|
+
|
|
6
|
+
ALTER TABLE payment_methods ADD COLUMN provider_token text;
|
|
7
|
+
|
|
8
|
+
UPDATE payment_methods pm
|
|
9
|
+
SET provider_token = m.provider_token
|
|
10
|
+
FROM card_token_map m
|
|
11
|
+
WHERE m.legacy_card_token = pm.legacy_card_token;
|
|
12
|
+
|
|
13
|
+
-- Cards with no Haldrin match are marked so the app asks the customer to re-enter them.
|
|
14
|
+
UPDATE payment_methods
|
|
15
|
+
SET status = 'needs_reentry'
|
|
16
|
+
WHERE provider_token IS NULL;
|
|
17
|
+
|
|
18
|
+
ALTER TABLE payment_methods DROP COLUMN legacy_card_token;
|
|
19
|
+
|
|
20
|
+
DROP TABLE card_token_map;
|
|
21
|
+
|
|
22
|
+
COMMIT;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# #billing-switch — notes copied from the channel, 4 Nov 2026
|
|
2
|
+
|
|
3
|
+
**Bea Lindgren** 10:12
|
|
4
|
+
Token import done. 31,640 cards in payment_methods, 31,228 matched in Haldrin's file. 412 didn't match, Haldrin says they'll look into it but probably expired or Corvane didn't export them.
|
|
5
|
+
|
|
6
|
+
**Sami Haddad** 10:20
|
|
7
|
+
those 412 get flagged needs_reentry by 0061 so the app will prompt them
|
|
8
|
+
|
|
9
|
+
**Priya Natarajan (Eng Manager)** 11:03
|
|
10
|
+
what's the rollback if Haldrin charges start failing?
|
|
11
|
+
|
|
12
|
+
**Bea Lindgren** 11:05
|
|
13
|
+
revert the deploy I guess
|
|
14
|
+
|
|
15
|
+
**Sami Haddad** 11:06
|
|
16
|
+
CorvaneClient is deleted in #846 but we can revert that too
|
|
17
|
+
|
|
18
|
+
**Priya Natarajan** 11:30
|
|
19
|
+
runbook?
|
|
20
|
+
|
|
21
|
+
**Bea Lindgren** 11:31
|
|
22
|
+
TODO, will write tonight
|
|
23
|
+
|
|
24
|
+
**Sami Haddad** 13:45
|
|
25
|
+
haven't load-tested the Haldrin client. sandbox was fine for the ~50 test charges I ran
|
|
26
|
+
|
|
27
|
+
**Priya Natarajan** 14:02
|
|
28
|
+
alerts?
|
|
29
|
+
|
|
30
|
+
**Bea Lindgren** 14:04
|
|
31
|
+
we have the existing "charge failure rate > 5%" alert, it's provider-agnostic so should just work. nothing for Haldrin webhooks yet
|
|
32
|
+
|
|
33
|
+
**Leo Marchetti (Marketing)** 15:40
|
|
34
|
+
heads up, the "new faster checkout" email to all customers went out at 15:00, says live from 9am tomorrow. blog post scheduled for 09:00.
|
|
35
|
+
|
|
36
|
+
**Priya Natarajan** 15:52
|
|
37
|
+
ok. who's on call tomorrow morning for billing?
|
|
38
|
+
|
|
39
|
+
**Sami Haddad** 15:53
|
|
40
|
+
me until 12, then nobody until Bea's back on Thursday
|