business-obligations 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ project follows [Semantic Versioning](https://semver.org/) with the data-aware p
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.3.1] - 2026-10-16
9
+
10
+ ### Fixed
11
+
12
+ - **data:** Minneapolis sick and safe time, Nashville license fee, Austin sourcing
13
+
8
14
  ## [0.3.0] - 2026-10-16
9
15
 
10
16
  ### Fixed
package/COVERAGE.md CHANGED
@@ -8,7 +8,7 @@ page on the date shown; everything not listed as checked is compiled from public
8
8
 
9
9
  - **Jurisdictions:** 51 (50 states and DC)
10
10
  - **States with unconfirmed fields:** 1
11
- - **Seeded cities:** 307: 164 with city-specific rules, 25 checked with no general city license or tax, 118 researched but unconfirmed (all other cities get the state's local licensing regime named for the city and county)
11
+ - **Seeded cities:** 307: 165 with city-specific rules, 25 checked with no general city license or tax, 117 researched but unconfirmed (all other cities get the state's local licensing regime named for the city and county)
12
12
  - **Seeded counties:** 312: 145 with county-specific filings (mainly business personal property returns), the rest researched but unconfirmed or with nothing county-level
13
13
  - **300 largest counties** (approximate): 297 of 300 covered
14
14
  - **300 largest cities** (2020 census; ranks beyond 100 are approximate): 300 of 300 covered
package/README.md CHANGED
@@ -111,7 +111,7 @@ them. Start with [CONTRIBUTING.md](https://github.com/Alisa007/business-obligati
111
111
  npm install business-obligations
112
112
  ```
113
113
 
114
- ESM and CommonJS are both supported. Node 20+. About 57 KB gzipped, no runtime dependencies.
114
+ ESM and CommonJS are both supported. Node 20+. About 115 KB gzipped, no runtime dependencies.
115
115
 
116
116
  ## Using it from plain JavaScript
117
117
 
@@ -413,7 +413,7 @@ It sets no cookies, records no sessions and respects Do Not Track. It is off unl
413
413
 
414
414
  ## Size
415
415
 
416
- The core bundle is about 57 KB gzipped with all 51 jurisdictions' data. ZIP lookup (about 305 KB gzipped) lives in the
416
+ The core bundle is about 115 KB gzipped with all 51 jurisdictions' data. ZIP lookup (about 305 KB gzipped) lives in the
417
417
  separate `business-obligations/zip` entry and is only loaded when you import it. `npm run size` enforces the budgets.
418
418
 
419
419
  ## Extending
@@ -2,11 +2,20 @@
2
2
  {
3
3
  "state": "MN",
4
4
  "city": "Minneapolis",
5
- "status": "unverified",
6
- "note": "Licensing appears to be industry-specific through Business Licenses and Consumer Services; the city's own list was not confirmed.",
7
5
  "counties": ["Hennepin"],
8
- "items": [],
9
- "checkedOn": "2026-10-13"
6
+ "items": [
7
+ {
8
+ "id": "minneapolis.sick-and-safe-time",
9
+ "title": "Provide Minneapolis sick and safe time",
10
+ "category": "recordkeeping",
11
+ "authority": "City of Minneapolis Labor Standards Enforcement",
12
+ "description": "Employers must give employees paid sick and safe time: 1 hour for every 30 hours worked, up to 48 hours a year and 80 hours overall, or front-load 48 hours a year. Keep a written policy and give it to new hires, display the Minneapolis labor standards poster in each language spoken by 5% or more of employees, and show employees the hours used and available each pay period.",
13
+ "frequency": "ongoing",
14
+ "url": "https://sicktimeinfo.minneapolismn.gov/employer-resources.html",
15
+ "appliesTo": ["employees"]
16
+ }
17
+ ],
18
+ "checkedOn": "2026-10-16"
10
19
  },
11
20
  {
12
21
  "state": "MN",
@@ -91,12 +91,13 @@
91
91
  "title": "Obtain Nashville / Davidson County business license",
92
92
  "category": "license",
93
93
  "authority": "Davidson County Clerk, Business Tax Division",
94
- "description": "Businesses in Nashville-Davidson County obtain a business license from the County Clerk and report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
94
+ "description": "Businesses in Nashville-Davidson County obtain a business tax license or a minimal activity license from the County Clerk. The license fee is $30 for a business inside Nashville city limits (Urban Service District) and $15 outside city limits but within Davidson County. Report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
95
95
  "frequency": "once",
96
96
  "url": "https://www.nashville.gov/departments/county-clerk/business-services/apply-business-license",
97
97
  "appliesTo": ["all"]
98
98
  }
99
99
  ],
100
- "aliases": ["Nashville-Davidson", "Metro Nashville"]
100
+ "aliases": ["Nashville-Davidson", "Metro Nashville"],
101
+ "checkedOn": "2026-10-16"
101
102
  }
102
103
  ]
@@ -30,11 +30,11 @@
30
30
  "state": "TX",
31
31
  "city": "Austin",
32
32
  "status": "unverified",
33
- "note": "The city's start-a-business page points to checking city and county requirements; no general license was confirmed.",
33
+ "note": "The city's start-a-business page says Texas has no general business license, that most Austin businesses need permits or licenses depending on type, location and activity (issued through Austin Development Services), and lists no general city business license. Business personal property tax is administered by the Travis County Appraisal District, not the city.",
34
34
  "counties": ["Travis"],
35
35
  "sourceUrl": "https://www.austintexas.gov/economic-development/start-business",
36
36
  "items": [],
37
- "checkedOn": "2026-10-13"
37
+ "checkedOn": "2026-10-16"
38
38
  },
39
39
  {
40
40
  "state": "TX",
package/dist/cli.js CHANGED
@@ -9868,11 +9868,20 @@ var MN_default2 = [
9868
9868
  {
9869
9869
  state: "MN",
9870
9870
  city: "Minneapolis",
9871
- status: "unverified",
9872
- note: "Licensing appears to be industry-specific through Business Licenses and Consumer Services; the city's own list was not confirmed.",
9873
9871
  counties: ["Hennepin"],
9874
- items: [],
9875
- checkedOn: "2026-10-13"
9872
+ items: [
9873
+ {
9874
+ id: "minneapolis.sick-and-safe-time",
9875
+ title: "Provide Minneapolis sick and safe time",
9876
+ category: "recordkeeping",
9877
+ authority: "City of Minneapolis Labor Standards Enforcement",
9878
+ description: "Employers must give employees paid sick and safe time: 1 hour for every 30 hours worked, up to 48 hours a year and 80 hours overall, or front-load 48 hours a year. Keep a written policy and give it to new hires, display the Minneapolis labor standards poster in each language spoken by 5% or more of employees, and show employees the hours used and available each pay period.",
9879
+ frequency: "ongoing",
9880
+ url: "https://sicktimeinfo.minneapolismn.gov/employer-resources.html",
9881
+ appliesTo: ["employees"]
9882
+ }
9883
+ ],
9884
+ checkedOn: "2026-10-16"
9876
9885
  },
9877
9886
  {
9878
9887
  state: "MN",
@@ -11085,13 +11094,14 @@ var TN_default2 = [
11085
11094
  title: "Obtain Nashville / Davidson County business license",
11086
11095
  category: "license",
11087
11096
  authority: "Davidson County Clerk, Business Tax Division",
11088
- description: "Businesses in Nashville-Davidson County obtain a business license from the County Clerk and report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
11097
+ description: "Businesses in Nashville-Davidson County obtain a business tax license or a minimal activity license from the County Clerk. The license fee is $30 for a business inside Nashville city limits (Urban Service District) and $15 outside city limits but within Davidson County. Report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
11089
11098
  frequency: "once",
11090
11099
  url: "https://www.nashville.gov/departments/county-clerk/business-services/apply-business-license",
11091
11100
  appliesTo: ["all"]
11092
11101
  }
11093
11102
  ],
11094
- aliases: ["Nashville-Davidson", "Metro Nashville"]
11103
+ aliases: ["Nashville-Davidson", "Metro Nashville"],
11104
+ checkedOn: "2026-10-16"
11095
11105
  }
11096
11106
  ];
11097
11107
 
@@ -11128,11 +11138,11 @@ var TX_default2 = [
11128
11138
  state: "TX",
11129
11139
  city: "Austin",
11130
11140
  status: "unverified",
11131
- note: "The city's start-a-business page points to checking city and county requirements; no general license was confirmed.",
11141
+ note: "The city's start-a-business page says Texas has no general business license, that most Austin businesses need permits or licenses depending on type, location and activity (issued through Austin Development Services), and lists no general city business license. Business personal property tax is administered by the Travis County Appraisal District, not the city.",
11132
11142
  counties: ["Travis"],
11133
11143
  sourceUrl: "https://www.austintexas.gov/economic-development/start-business",
11134
11144
  items: [],
11135
- checkedOn: "2026-10-13"
11145
+ checkedOn: "2026-10-16"
11136
11146
  },
11137
11147
  {
11138
11148
  state: "TX",
@@ -19083,6 +19093,11 @@ function todayIso(now = /* @__PURE__ */ new Date()) {
19083
19093
  const pad3 = (n) => String(n).padStart(2, "0");
19084
19094
  return `${now.getFullYear()}-${pad3(now.getMonth() + 1)}-${pad3(now.getDate())}`;
19085
19095
  }
19096
+ function addDays(iso2, days) {
19097
+ const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
19098
+ d.setUTCDate(d.getUTCDate() + days);
19099
+ return d.toISOString().slice(0, 10);
19100
+ }
19086
19101
 
19087
19102
  // src/validate.ts
19088
19103
  var ENUMS = {
@@ -19817,11 +19832,6 @@ function occurrencesBetween(due, ctx, from, to) {
19817
19832
 
19818
19833
  // src/provenance.ts
19819
19834
  var REVIEW_INTERVAL_DAYS = 365;
19820
- function addDays(iso2, days) {
19821
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
19822
- d.setUTCDate(d.getUTCDate() + days);
19823
- return d.toISOString().slice(0, 10);
19824
- }
19825
19835
  function sourceKind(url) {
19826
19836
  if (/\.pdf($|[?#])/i.test(url)) return "pdf";
19827
19837
  if (/municode|amlegal|ordinance|codelibrary/i.test(url)) return "ordinance";
@@ -20833,7 +20843,6 @@ function resolveProfile(input) {
20833
20843
  taxTreatment
20834
20844
  };
20835
20845
  }
20836
- var today = () => todayIso();
20837
20846
  function industryFromNaics(naics) {
20838
20847
  if (!naics) return void 0;
20839
20848
  if (naics.startsWith("23")) return "construction";
@@ -20849,14 +20858,9 @@ var RENEWAL_INTERVAL = {
20849
20858
  every_five_years: "five years"
20850
20859
  };
20851
20860
  var FEEDBACK_URL = "https://github.com/Alisa007/business-obligations/issues/new?template=data-correction.yml&title=";
20852
- var addDays2 = (iso2, days) => {
20853
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
20854
- d.setUTCDate(d.getUTCDate() + days);
20855
- return d.toISOString().slice(0, 10);
20856
- };
20857
20861
  function evaluate(input, options = {}) {
20858
20862
  const profile = resolveProfile(input);
20859
- const asOf = options.asOf ?? today();
20863
+ const asOf = options.asOf ?? todayIso();
20860
20864
  if (!isIsoDate(asOf)) throw new InvalidProfileError("`asOf` must be YYYY-MM-DD");
20861
20865
  const rules = options.rules ?? RULE_PACKS[profile.country];
20862
20866
  const changes = [...BUNDLED_CHANGES, ...options.changes ?? []];
@@ -20914,7 +20918,7 @@ function evaluate(input, options = {}) {
20914
20918
  }
20915
20919
  const states = /* @__PURE__ */ new Set([profile.formationState, ...profile.presence.map((x) => x.state)]);
20916
20920
  const relevant = (c) => c.scope.state ? states.has(c.scope.state) : !c.scope.rules || items.some((it) => changeMatchesItem(c, it));
20917
- const since = addDays2(asOf, -(options.recentDays ?? 365));
20921
+ const since = addDays(asOf, -(options.recentDays ?? 365));
20918
20922
  const upcomingChanges = changes.filter((c) => c.effective > asOf && relevant(c)).sort((a, b) => a.effective.localeCompare(b.effective)).map(toNotice);
20919
20923
  const recentChanges = changes.filter((c) => c.effective <= asOf && c.effective >= since && relevant(c)).sort((a, b) => b.effective.localeCompare(a.effective)).map(toNotice);
20920
20924
  return {
@@ -21014,12 +21018,6 @@ function estimateCosts(items, options = {}) {
21014
21018
  }
21015
21019
 
21016
21020
  // src/calendar.ts
21017
- var today2 = () => todayIso();
21018
- var addDays3 = (iso2, n) => {
21019
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
21020
- d.setUTCDate(d.getUTCDate() + n);
21021
- return d.toISOString().slice(0, 10);
21022
- };
21023
21021
  function toEvent(item, date) {
21024
21022
  return {
21025
21023
  date,
@@ -21037,9 +21035,9 @@ function toEvent(item, date) {
21037
21035
  };
21038
21036
  }
21039
21037
  function buildCalendar(input, options = {}) {
21040
- const from = options.from ?? today2();
21038
+ const from = options.from ?? todayIso();
21041
21039
  if (!isIsoDate(from)) throw new InvalidProfileError("`from` must be YYYY-MM-DD");
21042
- const to = options.to ?? addDays3(from, 365);
21040
+ const to = options.to ?? addDays(from, 365);
21043
21041
  if (!isIsoDate(to)) throw new InvalidProfileError("`to` must be YYYY-MM-DD");
21044
21042
  if (to < from) throw new InvalidProfileError("`to` must not be before `from`");
21045
21043
  const evalOptions = {
@@ -21055,7 +21053,7 @@ function buildCalendar(input, options = {}) {
21055
21053
  snapshots.push({ start: d, result: evaluate(input, { ...evalOptions, asOf: d }) });
21056
21054
  const events = [];
21057
21055
  snapshots.forEach((snap, i) => {
21058
- const end = i + 1 < snapshots.length ? addDays3(snapshots[i + 1].start, -1) : to;
21056
+ const end = i + 1 < snapshots.length ? addDays(snapshots[i + 1].start, -1) : to;
21059
21057
  for (const item of snap.result.items) {
21060
21058
  if (!item.due) continue;
21061
21059
  const dates = occurrencesBetween(
@@ -21092,7 +21090,7 @@ function fold(line) {
21092
21090
  }
21093
21091
  var compact = (iso2) => iso2.replace(/-/g, "");
21094
21092
  function toICS(events, options = {}) {
21095
- const stamp = compact(options.stamp ?? events[0]?.date ?? today2());
21093
+ const stamp = compact(options.stamp ?? events[0]?.date ?? todayIso());
21096
21094
  const lines = [
21097
21095
  "BEGIN:VCALENDAR",
21098
21096
  "VERSION:2.0",
@@ -21114,7 +21112,7 @@ function toICS(events, options = {}) {
21114
21112
  `UID:${e.itemId}-${e.date}@business-obligations`,
21115
21113
  `DTSTAMP:${stamp}T000000Z`,
21116
21114
  `DTSTART;VALUE=DATE:${compact(e.date)}`,
21117
- `DTEND;VALUE=DATE:${compact(addDays3(e.date, 1))}`,
21115
+ `DTEND;VALUE=DATE:${compact(addDays(e.date, 1))}`,
21118
21116
  `SUMMARY:${esc(e.title)}`,
21119
21117
  `DESCRIPTION:${esc(parts.join("\n"))}`,
21120
21118
  ...e.url ? [`URL:${e.url}`] : [],
@@ -21305,20 +21303,14 @@ function phaseOf(item) {
21305
21303
  if (item.category === "recordkeeping" || item.category === "insurance") return "employ";
21306
21304
  return "operate";
21307
21305
  }
21308
- var addDays4 = (iso2, n) => {
21309
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
21310
- d.setUTCDate(d.getUTCDate() + n);
21311
- return d.toISOString().slice(0, 10);
21312
- };
21313
21306
  var daysBetween = (a, b) => Math.round((Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`)) / 864e5);
21314
- var today3 = () => todayIso();
21315
21307
  function planLaunch(input, options = {}) {
21316
- const startDate = options.startDate ?? today3();
21308
+ const startDate = options.startDate ?? todayIso();
21317
21309
  const allowance = { ...DEFAULT_ALLOWANCE, ...options.allowanceDays };
21318
21310
  const windows = /* @__PURE__ */ new Map();
21319
21311
  let cursor = startDate;
21320
21312
  for (const id of ["prepare", "form", "identify", "register", "license", "employ"]) {
21321
- const end = addDays4(cursor, allowance[id]);
21313
+ const end = addDays(cursor, allowance[id]);
21322
21314
  windows.set(id, { start: cursor, end });
21323
21315
  cursor = end;
21324
21316
  }
@@ -21415,7 +21407,7 @@ function planLaunch(input, options = {}) {
21415
21407
  // package.json
21416
21408
  var package_default = {
21417
21409
  name: "business-obligations",
21418
- version: "0.3.0",
21410
+ version: "0.3.1",
21419
21411
  description: "Open-source TypeScript rules engine: describe a US business, get the licenses, permits, filings and deadlines it needs, ordered and sourced. Zero dependencies.",
21420
21412
  license: "MIT",
21421
21413
  type: "module",
package/dist/index.cjs CHANGED
@@ -9926,11 +9926,20 @@ var MN_default2 = [
9926
9926
  {
9927
9927
  state: "MN",
9928
9928
  city: "Minneapolis",
9929
- status: "unverified",
9930
- note: "Licensing appears to be industry-specific through Business Licenses and Consumer Services; the city's own list was not confirmed.",
9931
9929
  counties: ["Hennepin"],
9932
- items: [],
9933
- checkedOn: "2026-10-13"
9930
+ items: [
9931
+ {
9932
+ id: "minneapolis.sick-and-safe-time",
9933
+ title: "Provide Minneapolis sick and safe time",
9934
+ category: "recordkeeping",
9935
+ authority: "City of Minneapolis Labor Standards Enforcement",
9936
+ description: "Employers must give employees paid sick and safe time: 1 hour for every 30 hours worked, up to 48 hours a year and 80 hours overall, or front-load 48 hours a year. Keep a written policy and give it to new hires, display the Minneapolis labor standards poster in each language spoken by 5% or more of employees, and show employees the hours used and available each pay period.",
9937
+ frequency: "ongoing",
9938
+ url: "https://sicktimeinfo.minneapolismn.gov/employer-resources.html",
9939
+ appliesTo: ["employees"]
9940
+ }
9941
+ ],
9942
+ checkedOn: "2026-10-16"
9934
9943
  },
9935
9944
  {
9936
9945
  state: "MN",
@@ -11143,13 +11152,14 @@ var TN_default2 = [
11143
11152
  title: "Obtain Nashville / Davidson County business license",
11144
11153
  category: "license",
11145
11154
  authority: "Davidson County Clerk, Business Tax Division",
11146
- description: "Businesses in Nashville-Davidson County obtain a business license from the County Clerk and report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
11155
+ description: "Businesses in Nashville-Davidson County obtain a business tax license or a minimal activity license from the County Clerk. The license fee is $30 for a business inside Nashville city limits (Urban Service District) and $15 outside city limits but within Davidson County. Report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
11147
11156
  frequency: "once",
11148
11157
  url: "https://www.nashville.gov/departments/county-clerk/business-services/apply-business-license",
11149
11158
  appliesTo: ["all"]
11150
11159
  }
11151
11160
  ],
11152
- aliases: ["Nashville-Davidson", "Metro Nashville"]
11161
+ aliases: ["Nashville-Davidson", "Metro Nashville"],
11162
+ checkedOn: "2026-10-16"
11153
11163
  }
11154
11164
  ];
11155
11165
 
@@ -11186,11 +11196,11 @@ var TX_default2 = [
11186
11196
  state: "TX",
11187
11197
  city: "Austin",
11188
11198
  status: "unverified",
11189
- note: "The city's start-a-business page points to checking city and county requirements; no general license was confirmed.",
11199
+ note: "The city's start-a-business page says Texas has no general business license, that most Austin businesses need permits or licenses depending on type, location and activity (issued through Austin Development Services), and lists no general city business license. Business personal property tax is administered by the Travis County Appraisal District, not the city.",
11190
11200
  counties: ["Travis"],
11191
11201
  sourceUrl: "https://www.austintexas.gov/economic-development/start-business",
11192
11202
  items: [],
11193
- checkedOn: "2026-10-13"
11203
+ checkedOn: "2026-10-16"
11194
11204
  },
11195
11205
  {
11196
11206
  state: "TX",
@@ -19141,6 +19151,11 @@ function todayIso(now = /* @__PURE__ */ new Date()) {
19141
19151
  const pad3 = (n) => String(n).padStart(2, "0");
19142
19152
  return `${now.getFullYear()}-${pad3(now.getMonth() + 1)}-${pad3(now.getDate())}`;
19143
19153
  }
19154
+ function addDays(iso2, days) {
19155
+ const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
19156
+ d.setUTCDate(d.getUTCDate() + days);
19157
+ return d.toISOString().slice(0, 10);
19158
+ }
19144
19159
 
19145
19160
  // src/validate.ts
19146
19161
  var ENUMS = {
@@ -19876,11 +19891,6 @@ function occurrencesBetween(due, ctx, from, to) {
19876
19891
 
19877
19892
  // src/provenance.ts
19878
19893
  var REVIEW_INTERVAL_DAYS = 365;
19879
- function addDays(iso2, days) {
19880
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
19881
- d.setUTCDate(d.getUTCDate() + days);
19882
- return d.toISOString().slice(0, 10);
19883
- }
19884
19894
  function sourceKind(url) {
19885
19895
  if (/\.pdf($|[?#])/i.test(url)) return "pdf";
19886
19896
  if (/municode|amlegal|ordinance|codelibrary/i.test(url)) return "ordinance";
@@ -20892,7 +20902,6 @@ function resolveProfile(input) {
20892
20902
  taxTreatment
20893
20903
  };
20894
20904
  }
20895
- var today = () => todayIso();
20896
20905
  function industryFromNaics(naics) {
20897
20906
  if (!naics) return void 0;
20898
20907
  if (naics.startsWith("23")) return "construction";
@@ -20908,14 +20917,9 @@ var RENEWAL_INTERVAL = {
20908
20917
  every_five_years: "five years"
20909
20918
  };
20910
20919
  var FEEDBACK_URL = "https://github.com/Alisa007/business-obligations/issues/new?template=data-correction.yml&title=";
20911
- var addDays2 = (iso2, days) => {
20912
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
20913
- d.setUTCDate(d.getUTCDate() + days);
20914
- return d.toISOString().slice(0, 10);
20915
- };
20916
20920
  function evaluate(input, options = {}) {
20917
20921
  const profile = resolveProfile(input);
20918
- const asOf = options.asOf ?? today();
20922
+ const asOf = options.asOf ?? todayIso();
20919
20923
  if (!isIsoDate(asOf)) throw new InvalidProfileError("`asOf` must be YYYY-MM-DD");
20920
20924
  const rules = options.rules ?? RULE_PACKS[profile.country];
20921
20925
  const changes = [...BUNDLED_CHANGES, ...options.changes ?? []];
@@ -20973,7 +20977,7 @@ function evaluate(input, options = {}) {
20973
20977
  }
20974
20978
  const states = /* @__PURE__ */ new Set([profile.formationState, ...profile.presence.map((x) => x.state)]);
20975
20979
  const relevant = (c) => c.scope.state ? states.has(c.scope.state) : !c.scope.rules || items.some((it) => changeMatchesItem(c, it));
20976
- const since = addDays2(asOf, -(options.recentDays ?? 365));
20980
+ const since = addDays(asOf, -(options.recentDays ?? 365));
20977
20981
  const upcomingChanges = changes.filter((c) => c.effective > asOf && relevant(c)).sort((a, b) => a.effective.localeCompare(b.effective)).map(toNotice);
20978
20982
  const recentChanges = changes.filter((c) => c.effective <= asOf && c.effective >= since && relevant(c)).sort((a, b) => b.effective.localeCompare(a.effective)).map(toNotice);
20979
20983
  return {
@@ -21018,7 +21022,7 @@ function diffItems(before, after) {
21018
21022
  };
21019
21023
  }
21020
21024
  function previewChanges(input, options) {
21021
- const from = options.from ?? today();
21025
+ const from = options.from ?? todayIso();
21022
21026
  const base = { rules: options.rules, changes: options.changes };
21023
21027
  const before = evaluate(input, { ...base, asOf: from });
21024
21028
  const after = evaluate(input, { ...base, asOf: options.to });
@@ -21113,12 +21117,6 @@ function estimateCosts(items, options = {}) {
21113
21117
  }
21114
21118
 
21115
21119
  // src/calendar.ts
21116
- var today2 = () => todayIso();
21117
- var addDays3 = (iso2, n) => {
21118
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
21119
- d.setUTCDate(d.getUTCDate() + n);
21120
- return d.toISOString().slice(0, 10);
21121
- };
21122
21120
  function toEvent(item, date) {
21123
21121
  return {
21124
21122
  date,
@@ -21136,9 +21134,9 @@ function toEvent(item, date) {
21136
21134
  };
21137
21135
  }
21138
21136
  function buildCalendar(input, options = {}) {
21139
- const from = options.from ?? today2();
21137
+ const from = options.from ?? todayIso();
21140
21138
  if (!isIsoDate(from)) throw new InvalidProfileError("`from` must be YYYY-MM-DD");
21141
- const to = options.to ?? addDays3(from, 365);
21139
+ const to = options.to ?? addDays(from, 365);
21142
21140
  if (!isIsoDate(to)) throw new InvalidProfileError("`to` must be YYYY-MM-DD");
21143
21141
  if (to < from) throw new InvalidProfileError("`to` must not be before `from`");
21144
21142
  const evalOptions = {
@@ -21154,7 +21152,7 @@ function buildCalendar(input, options = {}) {
21154
21152
  snapshots.push({ start: d, result: evaluate(input, { ...evalOptions, asOf: d }) });
21155
21153
  const events = [];
21156
21154
  snapshots.forEach((snap, i) => {
21157
- const end = i + 1 < snapshots.length ? addDays3(snapshots[i + 1].start, -1) : to;
21155
+ const end = i + 1 < snapshots.length ? addDays(snapshots[i + 1].start, -1) : to;
21158
21156
  for (const item of snap.result.items) {
21159
21157
  if (!item.due) continue;
21160
21158
  const dates = occurrencesBetween(
@@ -21191,7 +21189,7 @@ function fold(line) {
21191
21189
  }
21192
21190
  var compact = (iso2) => iso2.replace(/-/g, "");
21193
21191
  function toICS(events, options = {}) {
21194
- const stamp = compact(options.stamp ?? events[0]?.date ?? today2());
21192
+ const stamp = compact(options.stamp ?? events[0]?.date ?? todayIso());
21195
21193
  const lines = [
21196
21194
  "BEGIN:VCALENDAR",
21197
21195
  "VERSION:2.0",
@@ -21213,7 +21211,7 @@ function toICS(events, options = {}) {
21213
21211
  `UID:${e.itemId}-${e.date}@business-obligations`,
21214
21212
  `DTSTAMP:${stamp}T000000Z`,
21215
21213
  `DTSTART;VALUE=DATE:${compact(e.date)}`,
21216
- `DTEND;VALUE=DATE:${compact(addDays3(e.date, 1))}`,
21214
+ `DTEND;VALUE=DATE:${compact(addDays(e.date, 1))}`,
21217
21215
  `SUMMARY:${esc(e.title)}`,
21218
21216
  `DESCRIPTION:${esc(parts.join("\n"))}`,
21219
21217
  ...e.url ? [`URL:${e.url}`] : [],
@@ -21528,20 +21526,14 @@ function phaseOf(item) {
21528
21526
  if (item.category === "recordkeeping" || item.category === "insurance") return "employ";
21529
21527
  return "operate";
21530
21528
  }
21531
- var addDays4 = (iso2, n) => {
21532
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
21533
- d.setUTCDate(d.getUTCDate() + n);
21534
- return d.toISOString().slice(0, 10);
21535
- };
21536
21529
  var daysBetween = (a, b) => Math.round((Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`)) / 864e5);
21537
- var today3 = () => todayIso();
21538
21530
  function planLaunch(input, options = {}) {
21539
- const startDate = options.startDate ?? today3();
21531
+ const startDate = options.startDate ?? todayIso();
21540
21532
  const allowance = { ...DEFAULT_ALLOWANCE, ...options.allowanceDays };
21541
21533
  const windows = /* @__PURE__ */ new Map();
21542
21534
  let cursor = startDate;
21543
21535
  for (const id of ["prepare", "form", "identify", "register", "license", "employ"]) {
21544
- const end = addDays4(cursor, allowance[id]);
21536
+ const end = addDays(cursor, allowance[id]);
21545
21537
  windows.set(id, { start: cursor, end });
21546
21538
  cursor = end;
21547
21539
  }
package/dist/index.js CHANGED
@@ -9863,11 +9863,20 @@ var MN_default2 = [
9863
9863
  {
9864
9864
  state: "MN",
9865
9865
  city: "Minneapolis",
9866
- status: "unverified",
9867
- note: "Licensing appears to be industry-specific through Business Licenses and Consumer Services; the city's own list was not confirmed.",
9868
9866
  counties: ["Hennepin"],
9869
- items: [],
9870
- checkedOn: "2026-10-13"
9867
+ items: [
9868
+ {
9869
+ id: "minneapolis.sick-and-safe-time",
9870
+ title: "Provide Minneapolis sick and safe time",
9871
+ category: "recordkeeping",
9872
+ authority: "City of Minneapolis Labor Standards Enforcement",
9873
+ description: "Employers must give employees paid sick and safe time: 1 hour for every 30 hours worked, up to 48 hours a year and 80 hours overall, or front-load 48 hours a year. Keep a written policy and give it to new hires, display the Minneapolis labor standards poster in each language spoken by 5% or more of employees, and show employees the hours used and available each pay period.",
9874
+ frequency: "ongoing",
9875
+ url: "https://sicktimeinfo.minneapolismn.gov/employer-resources.html",
9876
+ appliesTo: ["employees"]
9877
+ }
9878
+ ],
9879
+ checkedOn: "2026-10-16"
9871
9880
  },
9872
9881
  {
9873
9882
  state: "MN",
@@ -11080,13 +11089,14 @@ var TN_default2 = [
11080
11089
  title: "Obtain Nashville / Davidson County business license",
11081
11090
  category: "license",
11082
11091
  authority: "Davidson County Clerk, Business Tax Division",
11083
- description: "Businesses in Nashville-Davidson County obtain a business license from the County Clerk and report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
11092
+ description: "Businesses in Nashville-Davidson County obtain a business tax license or a minimal activity license from the County Clerk. The license fee is $30 for a business inside Nashville city limits (Urban Service District) and $15 outside city limits but within Davidson County. Report gross receipts to the Tennessee Department of Revenue with the annual business tax return.",
11084
11093
  frequency: "once",
11085
11094
  url: "https://www.nashville.gov/departments/county-clerk/business-services/apply-business-license",
11086
11095
  appliesTo: ["all"]
11087
11096
  }
11088
11097
  ],
11089
- aliases: ["Nashville-Davidson", "Metro Nashville"]
11098
+ aliases: ["Nashville-Davidson", "Metro Nashville"],
11099
+ checkedOn: "2026-10-16"
11090
11100
  }
11091
11101
  ];
11092
11102
 
@@ -11123,11 +11133,11 @@ var TX_default2 = [
11123
11133
  state: "TX",
11124
11134
  city: "Austin",
11125
11135
  status: "unverified",
11126
- note: "The city's start-a-business page points to checking city and county requirements; no general license was confirmed.",
11136
+ note: "The city's start-a-business page says Texas has no general business license, that most Austin businesses need permits or licenses depending on type, location and activity (issued through Austin Development Services), and lists no general city business license. Business personal property tax is administered by the Travis County Appraisal District, not the city.",
11127
11137
  counties: ["Travis"],
11128
11138
  sourceUrl: "https://www.austintexas.gov/economic-development/start-business",
11129
11139
  items: [],
11130
- checkedOn: "2026-10-13"
11140
+ checkedOn: "2026-10-16"
11131
11141
  },
11132
11142
  {
11133
11143
  state: "TX",
@@ -19078,6 +19088,11 @@ function todayIso(now = /* @__PURE__ */ new Date()) {
19078
19088
  const pad3 = (n) => String(n).padStart(2, "0");
19079
19089
  return `${now.getFullYear()}-${pad3(now.getMonth() + 1)}-${pad3(now.getDate())}`;
19080
19090
  }
19091
+ function addDays(iso2, days) {
19092
+ const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
19093
+ d.setUTCDate(d.getUTCDate() + days);
19094
+ return d.toISOString().slice(0, 10);
19095
+ }
19081
19096
 
19082
19097
  // src/validate.ts
19083
19098
  var ENUMS = {
@@ -19813,11 +19828,6 @@ function occurrencesBetween(due, ctx, from, to) {
19813
19828
 
19814
19829
  // src/provenance.ts
19815
19830
  var REVIEW_INTERVAL_DAYS = 365;
19816
- function addDays(iso2, days) {
19817
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
19818
- d.setUTCDate(d.getUTCDate() + days);
19819
- return d.toISOString().slice(0, 10);
19820
- }
19821
19831
  function sourceKind(url) {
19822
19832
  if (/\.pdf($|[?#])/i.test(url)) return "pdf";
19823
19833
  if (/municode|amlegal|ordinance|codelibrary/i.test(url)) return "ordinance";
@@ -20829,7 +20839,6 @@ function resolveProfile(input) {
20829
20839
  taxTreatment
20830
20840
  };
20831
20841
  }
20832
- var today = () => todayIso();
20833
20842
  function industryFromNaics(naics) {
20834
20843
  if (!naics) return void 0;
20835
20844
  if (naics.startsWith("23")) return "construction";
@@ -20845,14 +20854,9 @@ var RENEWAL_INTERVAL = {
20845
20854
  every_five_years: "five years"
20846
20855
  };
20847
20856
  var FEEDBACK_URL = "https://github.com/Alisa007/business-obligations/issues/new?template=data-correction.yml&title=";
20848
- var addDays2 = (iso2, days) => {
20849
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
20850
- d.setUTCDate(d.getUTCDate() + days);
20851
- return d.toISOString().slice(0, 10);
20852
- };
20853
20857
  function evaluate(input, options = {}) {
20854
20858
  const profile = resolveProfile(input);
20855
- const asOf = options.asOf ?? today();
20859
+ const asOf = options.asOf ?? todayIso();
20856
20860
  if (!isIsoDate(asOf)) throw new InvalidProfileError("`asOf` must be YYYY-MM-DD");
20857
20861
  const rules = options.rules ?? RULE_PACKS[profile.country];
20858
20862
  const changes = [...BUNDLED_CHANGES, ...options.changes ?? []];
@@ -20910,7 +20914,7 @@ function evaluate(input, options = {}) {
20910
20914
  }
20911
20915
  const states = /* @__PURE__ */ new Set([profile.formationState, ...profile.presence.map((x) => x.state)]);
20912
20916
  const relevant = (c) => c.scope.state ? states.has(c.scope.state) : !c.scope.rules || items.some((it) => changeMatchesItem(c, it));
20913
- const since = addDays2(asOf, -(options.recentDays ?? 365));
20917
+ const since = addDays(asOf, -(options.recentDays ?? 365));
20914
20918
  const upcomingChanges = changes.filter((c) => c.effective > asOf && relevant(c)).sort((a, b) => a.effective.localeCompare(b.effective)).map(toNotice);
20915
20919
  const recentChanges = changes.filter((c) => c.effective <= asOf && c.effective >= since && relevant(c)).sort((a, b) => b.effective.localeCompare(a.effective)).map(toNotice);
20916
20920
  return {
@@ -20955,7 +20959,7 @@ function diffItems(before, after) {
20955
20959
  };
20956
20960
  }
20957
20961
  function previewChanges(input, options) {
20958
- const from = options.from ?? today();
20962
+ const from = options.from ?? todayIso();
20959
20963
  const base = { rules: options.rules, changes: options.changes };
20960
20964
  const before = evaluate(input, { ...base, asOf: from });
20961
20965
  const after = evaluate(input, { ...base, asOf: options.to });
@@ -21050,12 +21054,6 @@ function estimateCosts(items, options = {}) {
21050
21054
  }
21051
21055
 
21052
21056
  // src/calendar.ts
21053
- var today2 = () => todayIso();
21054
- var addDays3 = (iso2, n) => {
21055
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
21056
- d.setUTCDate(d.getUTCDate() + n);
21057
- return d.toISOString().slice(0, 10);
21058
- };
21059
21057
  function toEvent(item, date) {
21060
21058
  return {
21061
21059
  date,
@@ -21073,9 +21071,9 @@ function toEvent(item, date) {
21073
21071
  };
21074
21072
  }
21075
21073
  function buildCalendar(input, options = {}) {
21076
- const from = options.from ?? today2();
21074
+ const from = options.from ?? todayIso();
21077
21075
  if (!isIsoDate(from)) throw new InvalidProfileError("`from` must be YYYY-MM-DD");
21078
- const to = options.to ?? addDays3(from, 365);
21076
+ const to = options.to ?? addDays(from, 365);
21079
21077
  if (!isIsoDate(to)) throw new InvalidProfileError("`to` must be YYYY-MM-DD");
21080
21078
  if (to < from) throw new InvalidProfileError("`to` must not be before `from`");
21081
21079
  const evalOptions = {
@@ -21091,7 +21089,7 @@ function buildCalendar(input, options = {}) {
21091
21089
  snapshots.push({ start: d, result: evaluate(input, { ...evalOptions, asOf: d }) });
21092
21090
  const events = [];
21093
21091
  snapshots.forEach((snap, i) => {
21094
- const end = i + 1 < snapshots.length ? addDays3(snapshots[i + 1].start, -1) : to;
21092
+ const end = i + 1 < snapshots.length ? addDays(snapshots[i + 1].start, -1) : to;
21095
21093
  for (const item of snap.result.items) {
21096
21094
  if (!item.due) continue;
21097
21095
  const dates = occurrencesBetween(
@@ -21128,7 +21126,7 @@ function fold(line) {
21128
21126
  }
21129
21127
  var compact = (iso2) => iso2.replace(/-/g, "");
21130
21128
  function toICS(events, options = {}) {
21131
- const stamp = compact(options.stamp ?? events[0]?.date ?? today2());
21129
+ const stamp = compact(options.stamp ?? events[0]?.date ?? todayIso());
21132
21130
  const lines = [
21133
21131
  "BEGIN:VCALENDAR",
21134
21132
  "VERSION:2.0",
@@ -21150,7 +21148,7 @@ function toICS(events, options = {}) {
21150
21148
  `UID:${e.itemId}-${e.date}@business-obligations`,
21151
21149
  `DTSTAMP:${stamp}T000000Z`,
21152
21150
  `DTSTART;VALUE=DATE:${compact(e.date)}`,
21153
- `DTEND;VALUE=DATE:${compact(addDays3(e.date, 1))}`,
21151
+ `DTEND;VALUE=DATE:${compact(addDays(e.date, 1))}`,
21154
21152
  `SUMMARY:${esc(e.title)}`,
21155
21153
  `DESCRIPTION:${esc(parts.join("\n"))}`,
21156
21154
  ...e.url ? [`URL:${e.url}`] : [],
@@ -21465,20 +21463,14 @@ function phaseOf(item) {
21465
21463
  if (item.category === "recordkeeping" || item.category === "insurance") return "employ";
21466
21464
  return "operate";
21467
21465
  }
21468
- var addDays4 = (iso2, n) => {
21469
- const d = /* @__PURE__ */ new Date(`${iso2}T00:00:00Z`);
21470
- d.setUTCDate(d.getUTCDate() + n);
21471
- return d.toISOString().slice(0, 10);
21472
- };
21473
21466
  var daysBetween = (a, b) => Math.round((Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`)) / 864e5);
21474
- var today3 = () => todayIso();
21475
21467
  function planLaunch(input, options = {}) {
21476
- const startDate = options.startDate ?? today3();
21468
+ const startDate = options.startDate ?? todayIso();
21477
21469
  const allowance = { ...DEFAULT_ALLOWANCE, ...options.allowanceDays };
21478
21470
  const windows = /* @__PURE__ */ new Map();
21479
21471
  let cursor = startDate;
21480
21472
  for (const id of ["prepare", "form", "identify", "register", "license", "employ"]) {
21481
- const end = addDays4(cursor, allowance[id]);
21473
+ const end = addDays(cursor, allowance[id]);
21482
21474
  windows.set(id, { start: cursor, end });
21483
21475
  cursor = end;
21484
21476
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "business-obligations",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Open-source TypeScript rules engine: describe a US business, get the licenses, permits, filings and deadlines it needs, ordered and sourced. Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",