Skip to content

Commit

Permalink
Data model for racial disparities narrative (#351)
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed Mar 11, 2021
1 parent 7eab3a7 commit 50a0642
Show file tree
Hide file tree
Showing 19 changed files with 3,037 additions and 135 deletions.
12 changes: 6 additions & 6 deletions spotlight-api/core/demo_data/racial_disparities.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{"race_or_ethnicity": "AMERICAN_INDIAN_ALASKAN_NATIVE", "current_total_sentenced_count": "1456", "current_incarceration_sentence_count": "1151", "current_probation_sentence_count": "349", "current_dual_sentence_count": "204", "current_supervision_population": "356", "total_incarcerated_population_36_mo": "244", "total_supervision_population_36_mo": "620", "supervision_technical_count_36_mo": "900", "supervision_absconsion_count_36_mo": "290", "supervision_new_crime_count_36_mo": "443", "supervision_unknown_count_36_mo": "41", "total_parole_population_36_mo": "120", "total_probation_population_36_mo": "506", "probation_technical_count_36_mo": "367", "probation_absconsion_count_36_mo": "198", "probation_new_crime_count_36_mo": "222", "parole_technical_count_36_mo": "543", "parole_absconsion_count_36_mo": "97", "parole_new_crime_count_36_mo": "231", "total_state_population": "262", "current_ftr_participation_count": "114", "parole_release_count_36_mo": "36", "parole_unknown_count_36_mo": "14", "probation_unknown_count_36_mo": "30"}
{"race_or_ethnicity": "BLACK", "current_total_sentenced_count": "2440", "current_incarceration_sentence_count": "1613", "current_probation_sentence_count": "857", "current_dual_sentence_count": "61", "current_supervision_population": "881", "total_incarcerated_population_36_mo": "96", "total_supervision_population_36_mo": "510", "supervision_technical_count_36_mo": "270", "supervision_absconsion_count_36_mo": "570", "supervision_new_crime_count_36_mo": "810", "supervision_unknown_count_36_mo": "60", "total_parole_population_36_mo": "248", "total_probation_population_36_mo": "270", "probation_technical_count_36_mo": "60", "probation_absconsion_count_36_mo": "399", "probation_new_crime_count_36_mo": "421", "parole_technical_count_36_mo": "212", "parole_absconsion_count_36_mo": "174", "parole_new_crime_count_36_mo": "393", "total_state_population": "111", "current_ftr_participation_count": "109", "parole_release_count_36_mo": "118", "parole_unknown_count_36_mo": "31", "probation_unknown_count_36_mo": "30"}
{"race_or_ethnicity": "WHITE", "current_total_sentenced_count": "3309", "current_incarceration_sentence_count": "1946", "current_probation_sentence_count": "1373", "current_dual_sentence_count": "94", "current_supervision_population": "1421", "total_incarcerated_population_36_mo": "671", "total_supervision_population_36_mo": "430", "supervision_technical_count_36_mo": "250", "supervision_absconsion_count_36_mo": "590", "supervision_new_crime_count_36_mo": "470", "supervision_unknown_count_36_mo": "80", "total_parole_population_36_mo": "284", "total_probation_population_36_mo": "155", "probation_technical_count_36_mo": "227", "probation_absconsion_count_36_mo": "258", "probation_new_crime_count_36_mo": "246", "parole_technical_count_36_mo": "29", "parole_absconsion_count_36_mo": "338", "parole_new_crime_count_36_mo": "230", "total_state_population": "12123", "current_ftr_participation_count": "47", "parole_release_count_36_mo": "104", "parole_unknown_count_36_mo": "54", "probation_unknown_count_36_mo": "31"}
{"race_or_ethnicity": "HISPANIC", "current_total_sentenced_count": "2763", "current_incarceration_sentence_count": "422", "current_probation_sentence_count": "2441", "current_dual_sentence_count": "12", "current_supervision_population": "2503", "total_incarcerated_population_36_mo": "123", "total_supervision_population_36_mo": "501", "supervision_technical_count_36_mo": "840", "supervision_absconsion_count_36_mo": "350", "supervision_new_crime_count_36_mo": "470", "supervision_unknown_count_36_mo": "90", "total_parole_population_36_mo": "87", "total_probation_population_36_mo": "422", "probation_technical_count_36_mo": "370", "probation_absconsion_count_36_mo": "148", "probation_new_crime_count_36_mo": "99", "parole_technical_count_36_mo": "476", "parole_absconsion_count_36_mo": "204", "parole_new_crime_count_36_mo": "376", "total_state_population": "380", "current_ftr_participation_count": "152", "parole_release_count_36_mo": "50", "parole_unknown_count_36_mo": "51", "probation_unknown_count_36_mo": "44"}
{"race_or_ethnicity": "OTHER", "current_total_sentenced_count": "3452", "current_incarceration_sentence_count": "1061", "current_probation_sentence_count": "2491", "current_dual_sentence_count": "88", "current_supervision_population": "2585", "total_incarcerated_population_36_mo": "12", "total_supervision_population_36_mo": "600", "supervision_technical_count_36_mo": "130", "supervision_absconsion_count_36_mo": "90", "supervision_new_crime_count_36_mo": "140", "supervision_unknown_count_36_mo": "40", "total_parole_population_36_mo": "200", "total_probation_population_36_mo": "410", "probation_technical_count_36_mo": "40", "probation_absconsion_count_36_mo": "38", "probation_new_crime_count_36_mo": "91", "parole_technical_count_36_mo": "92", "parole_absconsion_count_36_mo": "52", "parole_new_crime_count_36_mo": "52", "total_state_population": "28", "current_ftr_participation_count": "85", "parole_release_count_36_mo": "61", "parole_unknown_count_36_mo": "24", "probation_unknown_count_36_mo": "18"}
{"race_or_ethnicity": "ALL", "current_total_sentenced_count": "13420", "current_incarceration_sentence_count": "6193", "current_probation_sentence_count": "7511", "current_dual_sentence_count": "459", "current_supervision_population": "7746", "total_incarcerated_population_36_mo": "1146", "total_supervision_population_36_mo": "2661", "supervision_technical_count_36_mo": "2390", "supervision_absconsion_count_36_mo": "1890", "supervision_new_crime_count_36_mo": "2333", "supervision_unknown_count_36_mo": "311", "total_parole_population_36_mo": "939", "total_probation_population_36_mo": "1763", "probation_technical_count_36_mo": "1064", "probation_absconsion_count_36_mo": "1041", "probation_new_crime_count_36_mo": "1079", "parole_technical_count_36_mo": "1352", "parole_absconsion_count_36_mo": "865", "parole_new_crime_count_36_mo": "1282", "total_state_population": "12904", "current_ftr_participation_count": "507", "parole_release_count_36_mo": "359", "parole_unknown_count_36_mo": "174", "probation_unknown_count_36_mo": "163"}
{"race_or_ethnicity": "AMERICAN_INDIAN_ALASKAN_NATIVE", "current_total_sentenced_count": "1704", "current_incarceration_sentence_count": "1151", "current_probation_sentence_count": "349", "current_dual_sentence_count": "204", "current_supervision_population": "356", "total_incarcerated_population_36_mo": "244", "total_supervision_population_36_mo": "620", "supervision_technical_count_36_mo": "900", "supervision_absconsion_count_36_mo": "290", "supervision_new_crime_count_36_mo": "443", "supervision_unknown_count_36_mo": "41", "total_parole_population_36_mo": "120", "total_probation_population_36_mo": "506", "probation_technical_count_36_mo": "367", "probation_absconsion_count_36_mo": "198", "probation_new_crime_count_36_mo": "222", "parole_technical_count_36_mo": "543", "parole_absconsion_count_36_mo": "97", "parole_new_crime_count_36_mo": "231", "total_state_population": "262", "current_ftr_participation_count": "114", "parole_release_count_36_mo": "36", "parole_unknown_count_36_mo": "14", "probation_unknown_count_36_mo": "30"}
{"race_or_ethnicity": "BLACK", "current_total_sentenced_count": "2531", "current_incarceration_sentence_count": "1613", "current_probation_sentence_count": "857", "current_dual_sentence_count": "61", "current_supervision_population": "881", "total_incarcerated_population_36_mo": "96", "total_supervision_population_36_mo": "510", "supervision_technical_count_36_mo": "270", "supervision_absconsion_count_36_mo": "570", "supervision_new_crime_count_36_mo": "810", "supervision_unknown_count_36_mo": "60", "total_parole_population_36_mo": "248", "total_probation_population_36_mo": "270", "probation_technical_count_36_mo": "60", "probation_absconsion_count_36_mo": "399", "probation_new_crime_count_36_mo": "421", "parole_technical_count_36_mo": "212", "parole_absconsion_count_36_mo": "174", "parole_new_crime_count_36_mo": "393", "total_state_population": "111", "current_ftr_participation_count": "109", "parole_release_count_36_mo": "118", "parole_unknown_count_36_mo": "31", "probation_unknown_count_36_mo": "30"}
{"race_or_ethnicity": "WHITE", "current_total_sentenced_count": "3413", "current_incarceration_sentence_count": "1946", "current_probation_sentence_count": "1373", "current_dual_sentence_count": "94", "current_supervision_population": "1421", "total_incarcerated_population_36_mo": "671", "total_supervision_population_36_mo": "430", "supervision_technical_count_36_mo": "250", "supervision_absconsion_count_36_mo": "590", "supervision_new_crime_count_36_mo": "470", "supervision_unknown_count_36_mo": "80", "total_parole_population_36_mo": "284", "total_probation_population_36_mo": "155", "probation_technical_count_36_mo": "227", "probation_absconsion_count_36_mo": "258", "probation_new_crime_count_36_mo": "246", "parole_technical_count_36_mo": "29", "parole_absconsion_count_36_mo": "338", "parole_new_crime_count_36_mo": "230", "total_state_population": "12123", "current_ftr_participation_count": "47", "parole_release_count_36_mo": "104", "parole_unknown_count_36_mo": "54", "probation_unknown_count_36_mo": "31"}
{"race_or_ethnicity": "HISPANIC", "current_total_sentenced_count": "2875", "current_incarceration_sentence_count": "422", "current_probation_sentence_count": "2441", "current_dual_sentence_count": "12", "current_supervision_population": "2503", "total_incarcerated_population_36_mo": "123", "total_supervision_population_36_mo": "501", "supervision_technical_count_36_mo": "840", "supervision_absconsion_count_36_mo": "350", "supervision_new_crime_count_36_mo": "470", "supervision_unknown_count_36_mo": "90", "total_parole_population_36_mo": "87", "total_probation_population_36_mo": "422", "probation_technical_count_36_mo": "370", "probation_absconsion_count_36_mo": "148", "probation_new_crime_count_36_mo": "99", "parole_technical_count_36_mo": "476", "parole_absconsion_count_36_mo": "204", "parole_new_crime_count_36_mo": "376", "total_state_population": "380", "current_ftr_participation_count": "152", "parole_release_count_36_mo": "50", "parole_unknown_count_36_mo": "51", "probation_unknown_count_36_mo": "44"}
{"race_or_ethnicity": "OTHER", "current_total_sentenced_count": "3640", "current_incarceration_sentence_count": "1061", "current_probation_sentence_count": "2491", "current_dual_sentence_count": "88", "current_supervision_population": "2585", "total_incarcerated_population_36_mo": "12", "total_supervision_population_36_mo": "600", "supervision_technical_count_36_mo": "130", "supervision_absconsion_count_36_mo": "90", "supervision_new_crime_count_36_mo": "140", "supervision_unknown_count_36_mo": "40", "total_parole_population_36_mo": "200", "total_probation_population_36_mo": "410", "probation_technical_count_36_mo": "40", "probation_absconsion_count_36_mo": "38", "probation_new_crime_count_36_mo": "91", "parole_technical_count_36_mo": "92", "parole_absconsion_count_36_mo": "52", "parole_new_crime_count_36_mo": "52", "total_state_population": "28", "current_ftr_participation_count": "85", "parole_release_count_36_mo": "61", "parole_unknown_count_36_mo": "24", "probation_unknown_count_36_mo": "18"}
{"race_or_ethnicity": "ALL", "current_total_sentenced_count": "14163", "current_incarceration_sentence_count": "6193", "current_probation_sentence_count": "7511", "current_dual_sentence_count": "459", "current_supervision_population": "7746", "total_incarcerated_population_36_mo": "1146", "total_supervision_population_36_mo": "2661", "supervision_technical_count_36_mo": "2390", "supervision_absconsion_count_36_mo": "1890", "supervision_new_crime_count_36_mo": "2333", "supervision_unknown_count_36_mo": "311", "total_parole_population_36_mo": "939", "total_probation_population_36_mo": "1763", "probation_technical_count_36_mo": "1064", "probation_absconsion_count_36_mo": "1041", "probation_new_crime_count_36_mo": "1079", "parole_technical_count_36_mo": "1352", "parole_absconsion_count_36_mo": "865", "parole_new_crime_count_36_mo": "1282", "total_state_population": "12904", "current_ftr_participation_count": "507", "parole_release_count_36_mo": "359", "parole_unknown_count_36_mo": "174", "probation_unknown_count_36_mo": "163"}
3 changes: 3 additions & 0 deletions spotlight-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/date-fns": "^2.6.0",
"@types/downloadjs": "^1.4.2",
"@types/lodash.isempty": "^4.4.6",
"@types/lodash.mapvalues": "^4.6.6",
"@types/lodash.xor": "^4.5.6",
"@types/qs": "^6.9.5",
"@types/reach__router": "^1.3.6",
Expand Down Expand Up @@ -60,6 +61,7 @@
"intersection-observer": "^0.12.0",
"jszip": "^3.6.0",
"lodash.isempty": "^4.4.0",
"lodash.mapvalues": "^4.6.0",
"lodash.xor": "^4.5.0",
"mobx": "^6.0.4",
"mobx-react-lite": "^3.0.1",
Expand Down Expand Up @@ -87,6 +89,7 @@
"styled-reset": "^4.3.3",
"topojson": "^3.0.2",
"typescript": "^4.0.0",
"upper-case-first": "^2.0.2",
"utility-types": "^3.10.0",
"wait-for-localhost": "^3.3.0"
},
Expand Down
13 changes: 13 additions & 0 deletions spotlight-client/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,16 @@ export const ERROR_MESSAGES = {
export const NAV_BAR_HEIGHT = 80;

export const FOOTER_HEIGHT = 248;

export const REVOCATION_TYPE_LABELS = {
ABSCOND: "Absconsion",
NEW_CRIME: "New offense",
TECHNICAL: "Technical violation",
UNKNOWN: "Unknown type",
};

export const SENTENCE_TYPE_LABELS = {
INCARCERATION: "Incarceration",
PROBATION: "Probation",
DUAL_SENTENCE: "Both",
};
12 changes: 12 additions & 0 deletions spotlight-client/src/contentApi/sources/us_nd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,18 @@ const content: TenantContent = {
topology: programRegionsTopology,
},
},
racialDisparitiesNarrative: {
chartLabels: {
totalPopulation: "Proportions of races in the state",
totalSentenced: "Proportions of races sentenced and under DOCR control",
paroleGrant: "People released on parole",
incarceratedPopulation: "Overall prison population",
otherGroups: "All other racial/ethnic groups",
programmingParticipants: "Free Through Recovery active participants",
supervisionPopulation: "People subject to supervision",
totalPopulationSentences: "All people sentenced and under DOCR control",
},
},
};

export default content;
16 changes: 16 additions & 0 deletions spotlight-client/src/contentApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type TenantContent = NamedEntity & {
topologies?: {
ProgramRegions: MapData;
};
racialDisparitiesNarrative?: RacialDisparitiesNarrativeContent;
};

// ============================
Expand Down Expand Up @@ -139,3 +140,18 @@ export type SystemNarrativeContent = {
introduction: string;
sections: SystemNarrativeSection[];
};

export type RacialDisparitiesChartLabels = {
totalPopulation: string;
totalSentenced: string;
paroleGrant: string;
incarceratedPopulation: string;
otherGroups: string;
programmingParticipants: string;
supervisionPopulation: string;
totalPopulationSentences: string;
};

export type RacialDisparitiesNarrativeContent = {
chartLabels: RacialDisparitiesChartLabels;
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,18 @@ import { advanceTo, clear } from "jest-date-mock";
import { runInAction } from "mobx";
import { DemographicViewList, getDemographicCategories } from "../demographics";
import {
fetchMetrics,
fetchAndTransformMetric,
HistoricalPopulationBreakdownRecord,
RawMetricData,
} from "../metricsApi";
import { reactImmediately } from "../testUtils";
import HistoricalPopulationBreakdownMetric from "./HistoricalPopulationBreakdownMetric";
import contentFixture from "./__fixtures__/tenant_content_exhaustive";

jest.mock("../metricsApi", () => ({
...jest.requireActual("../metricsApi"),
fetchMetrics: jest.fn(),
fetchAndTransformMetric: jest.fn(),
}));

// we're not actually going to use this
const mockResponseData: RawMetricData = [{ test: "whatever" }];
// this has ... considerably less than 20 years of data
const mockData: HistoricalPopulationBreakdownRecord[] = [
// data for totals
Expand Down Expand Up @@ -62,17 +59,15 @@ const imputedRecordBase = {
count: 0,
};

const mockedFetchMetrics = fetchMetrics as jest.MockedFunction<
typeof fetchMetrics
const mockedFetchAndTransformMetric = fetchAndTransformMetric as jest.MockedFunction<
typeof fetchAndTransformMetric
>;
const mockSourceFileName = "test_metric_response";
// we're taking a shortcut by creating test data that doesn't need to be transformed
const mockTransformer = () => [...mockData];
// we are mocking this so it won't be called anyway
const mockTransformer = jest.fn();

beforeEach(() => {
mockedFetchMetrics.mockResolvedValue({
[mockSourceFileName]: mockResponseData,
});
mockedFetchAndTransformMetric.mockResolvedValue([...mockData]);
});

afterEach(() => {
Expand Down
24 changes: 1 addition & 23 deletions spotlight-client/src/contentModels/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,17 @@ import {
when,
} from "mobx";
import { stripHtml } from "string-strip-html";
import { ERROR_MESSAGES } from "../constants";
import { LocalityLabels, MetricTypeId, TenantId } from "../contentApi/types";
import { DemographicView } from "../demographics";
import {
fetchMetrics,
RawMetricData,
DemographicFields,
LocalityFields,
SupervisionSuccessRateMonthlyRecord,
fetchAndTransformMetric,
} from "../metricsApi";
import { MetricRecord, CollectionMap } from "./types";

export async function fetchAndTransformMetric<RecordFormat>({
sourceFileName,
tenantId,
transformFn,
}: {
sourceFileName: string;
tenantId: TenantId;
transformFn: (d: RawMetricData) => RecordFormat[];
}): Promise<RecordFormat[]> {
const apiResponse = await fetchMetrics({
metricNames: [sourceFileName],
tenantId,
});

const rawData = apiResponse[sourceFileName];
if (rawData) {
return transformFn(rawData);
}
throw new Error(ERROR_MESSAGES.noMetricData);
}

export type BaseMetricConstructorOptions<RecordFormat extends MetricRecord> = {
id: MetricTypeId;
name: string;
Expand Down

0 comments on commit 50a0642

Please sign in to comment.