Skip to content

Commit

Permalink
Merge 41a5bf6 into 11b7349
Browse files Browse the repository at this point in the history
  • Loading branch information
nasaownsky committed Jun 9, 2022
2 parents 11b7349 + 41a5bf6 commit c969e2b
Show file tree
Hide file tree
Showing 16 changed files with 452 additions and 28 deletions.
6 changes: 6 additions & 0 deletions spotlight-client/src/DataStore/TenantStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
TenantId,
} from "../contentApi/types";
import RacialDisparitiesNarrative from "../contentModels/RacialDisparitiesNarrative";
import RidersNarrative from "../contentModels/RidersNarrative";
import type SystemNarrative from "../contentModels/SystemNarrative";
import Tenant, { createTenant } from "../contentModels/Tenant";
import type RootStore from "./RootStore";
Expand Down Expand Up @@ -104,6 +105,7 @@ export default class TenantStore {
get currentNarrative():
| SystemNarrative
| RacialDisparitiesNarrative
| RidersNarrative
| undefined {
const { currentNarrativeTypeId, currentTenant } = this;
if (!currentNarrativeTypeId || !currentTenant) return undefined;
Expand All @@ -112,6 +114,10 @@ export default class TenantStore {
return currentTenant.systemNarratives[currentNarrativeTypeId];
}

if (currentNarrativeTypeId === "Riders") {
return currentTenant.ridersNarrative;
}

return currentTenant.racialDisparitiesNarrative;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const OtherNarrativeLinks = (): React.ReactElement | null => {
const narrativesToDisplay = [
...Object.values(tenant.systemNarratives),
tenant.racialDisparitiesNarrative,
tenant.ridersNarrative,
].filter((narrative): narrative is Narrative => {
if (narrative === undefined) return false;
return narrative.id !== currentNarrativeTypeId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,23 @@ import { observer } from "mobx-react-lite";
import React from "react";
import RacialDisparitiesNarrative from "../contentModels/RacialDisparitiesNarrative";
import { useDataStore } from "../StoreProvider";
import RacialDisparitiesNarrativePage from "./RacialDisparitiesNarrativePage";
import RacialDisparitiesNarrativePage from "../RacialDisparitiesNarrativePage/RacialDisparitiesNarrativePage";
import RidersNarrative from "../contentModels/RidersNarrative";
import RidersNarrativePage from "./RidersNarrativePage";

const RacialDisparitiesNarrativePageContainer: React.FC = () => {
const OtherNarrativesPageContainer: React.FC = () => {
const { narrative } = useDataStore();

if (narrative instanceof RacialDisparitiesNarrative) {
if (narrative.isLoading === undefined) narrative.hydrate();
return <RacialDisparitiesNarrativePage narrative={narrative} />;
}

if (narrative instanceof RidersNarrative && narrative.id === "Riders") {
return <RidersNarrativePage narrative={narrative} />;
}

return null;
};

export default observer(RacialDisparitiesNarrativePageContainer);
export default observer(OtherNarrativesPageContainer);
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import HTMLReactParser from "html-react-parser";
import React from "react";
import {
NarrativeIntroContainer,
NarrativeIntroCopy,
NarrativeSectionBody,
NarrativeSectionTitle,
NarrativeTitle,
} from "../UiLibrary";
import { NarrativeLayout, StickySection } from "../NarrativeLayout";
import MetricVizMapper from "../MetricVizMapper";
import RidersNarrative from "../contentModels/RidersNarrative";

const RidersNarrativePage: React.FC<{
narrative: RidersNarrative;
}> = ({ narrative }) => {
return (
<NarrativeLayout
sections={[
{
title: narrative.title,
contents: (
<NarrativeIntroContainer>
<NarrativeTitle>{narrative.title}</NarrativeTitle>
<NarrativeIntroCopy>
{HTMLReactParser(narrative.introduction)}
</NarrativeIntroCopy>
</NarrativeIntroContainer>
),
},
...narrative.sections.map((section) => {
let contents;

// all sections except the conclusion should look like this
if (section.type === "text") {
contents = (
<StickySection
leftContents={
<NarrativeSectionTitle>{section.title}</NarrativeSectionTitle>
}
rightContents={
<NarrativeSectionBody>
{HTMLReactParser(section.body)}
</NarrativeSectionBody>
}
/>
);
} else {
contents = (
<StickySection
leftContents={
<>
<NarrativeSectionTitle>
{section.title}
</NarrativeSectionTitle>
<NarrativeSectionBody>
{HTMLReactParser(section.body)}
</NarrativeSectionBody>
</>
}
rightContents={<MetricVizMapper metric={section.metric} />}
/>
);
}

return {
title: section.title,
contents,
};
}),
]}
/>
);
};

export default RidersNarrativePage;
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

export { default } from "./RacialDisparitiesNarrativePageContainer";
export { default } from "./OtherNarrativesPageContainer";
4 changes: 2 additions & 2 deletions spotlight-client/src/PageNarrative/PageNarrative.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import useBreakpoint from "@w11r/use-breakpoint";
import React from "react";
import { isSystemNarrativeTypeId, NarrativeTypeId } from "../contentApi/types";
import NarrativeFooter from "../NarrativeFooter";
import RacialDisparitiesNarrativePage from "../RacialDisparitiesNarrativePage";
import OtherNarrativesPageContainer from "../OtherNarrativesPageContainer";
import { NarrativesSlug } from "../routerUtils/types";
import SystemNarrativePage from "../SystemNarrativePage";
import withRouteSync from "../withRouteSync";
Expand All @@ -41,7 +41,7 @@ const PageNarrative: React.FC<PageNarrativeProps> = ({ narrativeTypeId }) => {
{isSystemNarrativeTypeId(narrativeTypeId) ? (
<SystemNarrativePage />
) : (
<RacialDisparitiesNarrativePage />
<OtherNarrativesPageContainer />
)}
{showFooter && <NarrativeFooter />}
</>
Expand Down
6 changes: 6 additions & 0 deletions spotlight-client/src/SiteNavigation/SiteNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ const SiteNavigation: React.FC<ShareButtonProps> = ({ openShareModal }) => {
label: tenant.racialDisparitiesNarrative.title,
});
}
if (tenant.ridersNarrative) {
narrativeOptions.push({
id: "Riders",
label: tenant.ridersNarrative.title,
});
}
}

return (
Expand Down
49 changes: 49 additions & 0 deletions spotlight-client/src/contentApi/sources/us_id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,22 @@ const content: TenantContent = {
probation revocation admission are included in the probation page.</p>
${demographicsBoilerplate} ${paroleBoilerplate}`,
},
RidersPopulationHistorical: {
name: "Population Over Time",
methodology: `tbd`,
},
RidersPopulationCurrent: {
name: "Current population of Rider",
methodology: `tbd`,
},
RidersOriginalCharge: {
name: "Original Charge",
methodology: `tbd`,
},
RidersReincarcerationRate: {
name: "Reincarceration Rate",
methodology: `tbd`,
},
},
systemNarratives: {
Sentencing: {
Expand Down Expand Up @@ -788,6 +804,39 @@ const content: TenantContent = {
},
},
},
ridersNarrative: {
title: "Rider Program",
introduction: `Retained jurisdiction or "riders" are individuals whom the court has retained jurisdiction over sentenced to a period of incarceration in an IDOC facility.
The IDOC assesses riders to determine their needs and places them in the appropriate facilities to receive intensive programming and education. Upon completion of a rider, the court determines whether to place the resident on probation or sentence them to term.`,
sections: [
{
title: "How prevalent are “riders” in the IDOC population?",
body: `The Rider program was first introduced in 2005 as an alternative to a longer prison sentence. Since then, the Rider population has risen to the point that 16% of IDOC’s institutional population is comprised of people on retained jurisdiction.`,
metricTypeId: "RidersPopulationHistorical",
},
{
title: "Who is on a Rider in Idaho?",
body:
"Some insights about the demographic composition of riders as compared to termers in Idaho.",
metricTypeId: "RidersPopulationCurrent",
},
{
title: "Why are people placed on Riders?",
body: `In general, riders are considered a last resort” before full incarceration. However, most people placed on riders have not committed a serious or violent offense; XX% of riders in the past three years have been for drug use or possession.`,
metricTypeId: "RidersOriginalCharge",
},
{
title: "What is the reincarceration rate for riders?",
body: `Despite high programming completion rates during rider sentences, those individuals tend to return to prison at higher rates compared to similar individuals who were simply sent to probation. XX% of riders return to prison within three years as compared to YY% of those on probation in a similar time period.`,
metricTypeId: "RidersReincarcerationRate",
},
{
title: "Where do we go from here? ",
body: `<div><p>Instead of sending low-level offenders to Riders, IDOC believes that sending the same people to treatment programs outside of prison (as conditions of probation or otherwise) would result in better social outcomes and more money saved that would otherwise be spent on incarceration. Money saved can be reinvested into community-based treatment programs to prevent people from entering the system in the first place.</p></div>`,
type: "text",
},
],
},
};

export default content;
46 changes: 38 additions & 8 deletions spotlight-client/src/contentApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export type TenantContent = {
"PrisonAdmissionReasonsCurrent"
>]?: MetricContent & { fieldMapping?: CategoryFieldMapping[] };
} &
{ [key in MetricTypeId]?: MetricContent };
{ [key in MetricTypeId]?: MetricContent } &
{ [key in RidersMetricTypeId]?: MetricContent };
systemNarratives: {
[key in SystemNarrativeTypeId]?: SystemNarrativeContent;
};
Expand All @@ -88,6 +89,7 @@ export type TenantContent = {
ProgramRegions: MapData;
};
racialDisparitiesNarrative?: RacialDisparitiesNarrativeContent;
ridersNarrative?: RidersNarrativeContent;
// if categories are enumerated for any of the keys here, they will be the only ones used;
// otherwise categories default to including all values in the associated unions
demographicCategories?: DemographicCategoryFilter;
Expand Down Expand Up @@ -119,9 +121,20 @@ export const MetricTypeIdList = [
"ParoleRevocationsAggregate",
"ParoleProgrammingCurrent",
] as const;
export const RidersMetricTypeIdList = [
"RidersPopulationHistorical",
"RidersPopulationCurrent",
"RidersOriginalCharge",
"RidersReincarcerationRate",
] as const;
export type MetricTypeId = typeof MetricTypeIdList[number];
export function isMetricTypeId(x: string): x is MetricTypeId {
return MetricTypeIdList.includes(x as MetricTypeId);
export type RidersMetricTypeId = typeof RidersMetricTypeIdList[number];

export type AllMetricsTypeId = MetricTypeId | RidersMetricTypeId;
export function isMetricTypeId(x: string): x is AllMetricsTypeId {
return [...MetricTypeIdList, ...RidersMetricTypeIdList].includes(
x as AllMetricsTypeId
);
}

type MetricContent = { name: string; methodology: string };
Expand All @@ -144,9 +157,14 @@ export function isSystemNarrativeTypeId(x: string): x is SystemNarrativeTypeId {
return SystemNarrativeTypeIdList.includes(x as SystemNarrativeTypeId);
}

export type NarrativeTypeId = SystemNarrativeTypeId | "RacialDisparities";
export type NarrativeTypeId =
| SystemNarrativeTypeId
| "RacialDisparities"
| "Riders";
export function isNarrativeTypeId(x: string): x is NarrativeTypeId {
return isSystemNarrativeTypeId(x) || x === "RacialDisparities";
return (
isSystemNarrativeTypeId(x) || x === "RacialDisparities" || x === "Riders"
);
}

type NarrativeSection = {
Expand All @@ -158,14 +176,26 @@ type SystemNarrativeSection = NarrativeSection & {
metricTypeId: MetricTypeId;
};

export type SystemNarrativeContent = {
type NarrativeContent = {
title: string;
previewTitle?: string;
preview: MetricTypeId;
introduction: string;
};

export type SystemNarrativeContent = NarrativeContent & {
previewTitle?: string;
preview?: MetricTypeId;
sections: SystemNarrativeSection[];
};

type RidersNarrativeSection = NarrativeSection & {
metricTypeId?: RidersMetricTypeId;
type?: "text" | "metric";
};

export type RidersNarrativeContent = NarrativeContent & {
sections: RidersNarrativeSection[];
};

export type RacialDisparitiesChartLabels = {
totalPopulation: string;
totalSentenced: string;
Expand Down
6 changes: 3 additions & 3 deletions spotlight-client/src/contentModels/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import {
when,
} from "mobx";
import {
AllMetricsTypeId,
DemographicCategoryFilter,
LocalityLabels,
MetricTypeId,
TenantId,
} from "../contentApi/types";
import RootStore from "../DataStore/RootStore";
Expand Down Expand Up @@ -73,7 +73,7 @@ function formatUnknownCounts(unknowns: UnknownCounts) {
}

export type BaseMetricConstructorOptions<RecordFormat extends MetricRecord> = {
id: MetricTypeId;
id: AllMetricsTypeId;
name: string;
methodology: string;
tenantId: TenantId;
Expand Down Expand Up @@ -105,7 +105,7 @@ export type BaseMetricConstructorOptions<RecordFormat extends MetricRecord> = {
export default abstract class Metric<RecordFormat extends MetricRecord>
implements Hydratable {
// metadata properties
readonly id: MetricTypeId;
readonly id: AllMetricsTypeId;

readonly methodology: string;

Expand Down

0 comments on commit c969e2b

Please sign in to comment.