Skip to content

Commit

Permalink
Add New York rules (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmreed committed Sep 27, 2023
1 parent 8671b49 commit 9ba1ef9
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 9 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
},
"type": "module",
"dependencies": {
"kolorist": "^1.8.0",
"remix-params-helper": "^0.4.10",
"zod": "^3.21.0"
}
Expand Down
1 change: 0 additions & 1 deletion src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="msapplication-TileColor" content="#1f2937" />
<meta name="theme-color" content="#1f2937" />
<link rel="script" src="/registerSW.js" />

%sveltekit.head%
</head>
Expand Down
5 changes: 4 additions & 1 deletion src/lib/CheckSituation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
situation={{ situation: params.situation, requestRequired: false }}
showIcon={false}
/>. The company is based in {localeName(params.companyLocation)}{#if params.employeeInLocation},
and already has employees in your location{/if}. The company has at least {params.totalEmployees}
and already has employees in your location{/if}. {#if params.officeSupervisorLocation !== OTHER_LOCALE}The
role reports to an office or non-remote supervisor in {localeName(
params.officeSupervisorLocation
)}.{/if} The company has at least {params.totalEmployees}
employee{#if params.totalEmployees !== 1}s{/if}. {#if params.roleLocation.length}The role can hire
in {#each params.roleLocation as roleLocation, i (roleLocation)}
{localeName(roleLocation)}{#if i < params.roleLocation.length - 1}, {#if i === params.roleLocation.length - 2}and
Expand Down
31 changes: 27 additions & 4 deletions src/lib/checking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const Params = z.object({
.string()
.default('')
.refine((s) => Object.keys(locales).includes(s) || s === '' || s === OTHER_LOCALE),
officeSupervisorLocation: z
.string()
.default('')
.refine((s) => Object.keys(locales).includes(s) || s === '' || s === OTHER_LOCALE),
employeeInLocation: z.boolean().default(false),
// This circumlocution avoids an issue with how Zod handles
// some input values, resulting in a parse failure if the user deletes the content of the field.
Expand Down Expand Up @@ -60,6 +64,7 @@ export function isValidParams(params: MatchParameters): boolean {
params.userLocation &&
(params.roleLocation.length > 0 || params.situation === Situation.Employed) &&
params.companyLocation &&
params.officeSupervisorLocation &&
params.totalEmployees
);
}
Expand Down Expand Up @@ -111,6 +116,15 @@ export function findMatchingLaws(
l.isOrContains(availableLocales[params.userLocation])
)
: [];
// And same rubric for supervisor/office locales.
const supervisorOfficeLocales =
params.officeSupervisorLocation !== OTHER_LOCALE
? Object.values(availableLocales).filter(
(l) =>
l.who.officeSupervisorInLocale &&
l.isOrContains(availableLocales[params.officeSupervisorLocation])
)
: [];

for (const thisLocale of Object.values(availableLocales)) {
const disclosureSituations = thisLocale.when.map((s) => s.situation).sort();
Expand All @@ -122,7 +136,15 @@ export function findMatchingLaws(
params.roleLocation.filter((eachLocale) =>
availableAllLocales[eachLocale].isOrContains(thisLocale)
).length > 0;
if (!isEligibleHireLocale && params.situation !== Situation.Employed) continue;
const isRelevantSupervisorLocale = supervisorOfficeLocales
.map((u) => thisLocale.isOrContains(u))
.some((f) => f);
if (
!isEligibleHireLocale &&
params.situation !== Situation.Employed &&
!isRelevantSupervisorLocale
)
continue;

// Would this locale's "when" rules apply to the user's situation?
const situationMatch =
Expand All @@ -135,11 +157,12 @@ export function findMatchingLaws(
!thisLocale.who.minEmployees || thisLocale.who.minEmployees <= params.totalEmployees;

if (situationMatch && totalEmployeeCountMatch) {
// Is this locality the same as or inside either the user's location or the company's?
// Is this locality the same as or encloses either the user's location or the company's?
// (A geographic match makes the fit easier to evaluate).
const geographicMatch = Boolean(
companyLocales.map((c) => c.isOrContains(thisLocale)).some((f) => f) ||
(userLocales.map((u) => u.isOrContains(thisLocale)).some((f) => f) &&
companyLocales.map((c) => thisLocale.isOrContains(c)).some((f) => f) ||
isRelevantSupervisorLocale ||
(userLocales.map((u) => thisLocale.isOrContains(u)).some((f) => f) &&
(thisLocale.who.minEmployeesInLocale || 0 <= 1) &&
(params.employeeInLocation || params.situation === Situation.Employed))
);
Expand Down
20 changes: 20 additions & 0 deletions src/lib/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export class Locale implements AbstractLocale {
interface WhoDisclosure {
officeInLocale?: boolean;
canHireInLocale?: boolean;
officeSupervisorInLocale?: boolean;
minEmployees?: number;
minEmployeesInLocale?: number;
}
Expand Down Expand Up @@ -372,6 +373,25 @@ const locales: Record<string, Locale> = [
},
legalUrl: 'https://codelibrary.amlegal.com/codes/toledo/latest/toledo_oh/0-0-0-159338',
reportViolationProcess: 'a private cause of action; no enforcement is done by the city'
}),
new Locale({
state: 'New York',
stateCode: 'NY',
strength: Strength.Strong,
what: {
salary: true
},
when: [{ situation: Situation.Interested }],
who: {
minEmployees: 4,
canHireInLocale: true,
officeSupervisorInLocale: true
},
referenceSource: 'New York Department of Labor',
referenceUrl: 'https://dol.ny.gov/pay-transparency',
reportViolationProcess: '',
reportViolationUrl: 'https://dol.ny.gov/pay-transparency',
legalUrl: 'https://www.nysenate.gov/legislation/bills/2023/S1326'
})
].reduce((map, locale) => {
map[locale.id] = locale;
Expand Down
16 changes: 16 additions & 0 deletions src/routes/find-your-rights/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@
{/each}
</select>
</label>
<label class="block" for="officeSupervisorLocation"
>Where is your office or non-remote supervisor located?
<aside class="italic text-xs">
If you don't know, or if your supervisor works remotely, choose "Somewhere else".
</aside>
<select
bind:value={$pageParams.officeSupervisorLocation}
id="officeSupervisorLocation"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-zinc-200 focus:ring-opacity-50"
>
{#each myLocationOptions as locale (locale[0])}
<option value={locale[0]}>{locale[1]}</option>
{/each}
</select>
</label>

<label class="block" for="totalEmployees">
How many total employees does the employer have?
<aside class="text-xs italic">
Expand Down
12 changes: 10 additions & 2 deletions src/routes/locations/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@
<br /><span class="italic text-xs"
>Note that this criterion is often poorly defined by the locale.</span
>
</li>{/if}
</li>
{/if}
{#if data.locale.who.canHireInLocale}
<li>
the role <strong>can be hired in {data.locale.name}</strong> (including remote);
</li>{/if}
</li>
{/if}
{#if data.locale.who.officeSupervisorInLocale}
<li>
the position is outside {data.locale.name}, but
<strong>reports to an office or non-remote supervisor located in {data.locale.name}; </strong>
</li>
{/if}
</ul>
<p>The employer must disclose:</p>
<ul class="pb-2 indent">
Expand Down
47 changes: 47 additions & 0 deletions src/tests/checking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ const locales: Record<string, Locale> = [
minEmployeesInLocale: 15,
officeInLocale: true
}
}),
new Locale({
state: 'New York',
stateCode: 'NY',
strength: Strength.Strong,
what: {
salary: true
},
when: [{ situation: Situation.Interested }],
who: {
officeSupervisorInLocale: true
}
})
].reduce((map, locale) => {
map[locale.id] = locale;
Expand Down Expand Up @@ -161,6 +173,7 @@ describe('validating parameters', () => {
situation: 1,
userLocation: 'california',
companyLocation: 'colorado',
officeSupervisorLocation: 'other',
employeeInLocation: true,
totalEmployees: 50,
roleLocation: 'california,colorado'
Expand All @@ -180,6 +193,7 @@ describe('validating parameters', () => {
situation: 1,
userLocation: 'california',
companyLocation: 'colorado',
officeSupervisorLocation: 'other',
employeeInLocation: true,
totalEmployees: 50
})
Expand All @@ -194,6 +208,7 @@ describe('matches simple laws', () => {
situation: Situation.Interested,
userLocation: 'california',
companyLocation: 'colorado',
officeSupervisorLocation: 'other',
employeeInLocation: true,
totalEmployees: 50,
roleLocation: ['california', 'colorado', 'washington', 'nevada']
Expand Down Expand Up @@ -240,6 +255,7 @@ describe('matches with placeholder role location', () => {
situation: Situation.Interested,
userLocation: 'california',
companyLocation: 'colorado',
officeSupervisorLocation: 'other',
employeeInLocation: true,
totalEmployees: 50,
roleLocation: ['us', 'other']
Expand Down Expand Up @@ -290,6 +306,7 @@ describe('matches with placeholder user location', () => {
situation: Situation.Interested,
userLocation: 'other',
companyLocation: 'colorado',
officeSupervisorLocation: 'other',
employeeInLocation: false,
totalEmployees: 50,
roleLocation: ['us', 'other']
Expand All @@ -316,6 +333,7 @@ describe('matches with sub-locations', () => {
situation: Situation.Interview,
userLocation: 'other',
companyLocation: 'nevada-goodsprings',
officeSupervisorLocation: 'other',
employeeInLocation: true,
totalEmployees: 50,
roleLocation: ['nevada']
Expand Down Expand Up @@ -346,6 +364,7 @@ describe('matches with sub-locations', () => {
situation: Situation.Interview,
userLocation: 'nevada-goodsprings',
companyLocation: 'california',
officeSupervisorLocation: 'other',
employeeInLocation: true,
totalEmployees: 50,
roleLocation: ['us']
Expand Down Expand Up @@ -378,6 +397,7 @@ describe('handles different situation thresholds', () => {
situation: Situation.Employed,
userLocation: 'colorado',
companyLocation: 'california',
officeSupervisorLocation: 'other',
employeeInLocation: false,
totalEmployees: 50,
roleLocation: ['us']
Expand All @@ -402,6 +422,7 @@ describe('handles different situation thresholds', () => {
situation: Situation.Employed,
userLocation: 'california',
companyLocation: 'colorado',
officeSupervisorLocation: 'other',
employeeInLocation: false,
totalEmployees: 50,
roleLocation: ['us']
Expand All @@ -426,6 +447,7 @@ describe('handles different situation thresholds', () => {
situation: Situation.Offer,
userLocation: 'other',
companyLocation: 'ohio-toledo',
officeSupervisorLocation: 'other',
employeeInLocation: false,
totalEmployees: 50,
roleLocation: ['us']
Expand All @@ -443,3 +465,28 @@ describe('handles different situation thresholds', () => {
});
});
});

it("handles New York's supervisor-location rubric", () => {
const matches = findMatchingLaws(
{
situation: Situation.Interested,
userLocation: 'other',
companyLocation: 'california',
officeSupervisorLocation: 'new-york',
employeeInLocation: false,
totalEmployees: 50,
roleLocation: ['california']
},
locales,
allLocales
);

expect(matches.length).toEqual(2);
expect(matches).toContainEqual({
locale: locales['new-york'],
earliestDisclosurePoint: Situation.Interested,
minEmployeesInLocale: 0,
what: { salary: true },
isGeoMatch: true
});
});
5 changes: 4 additions & 1 deletion svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ const config = {
],

kit: {
adapter: adapter()
adapter: adapter(),
serviceWorker: {
register: false
}
}
};

Expand Down

0 comments on commit 9ba1ef9

Please sign in to comment.