Skip to content

Commit

Permalink
Merge 8af4e1d into f4dde8d
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian authored Jan 28, 2021
2 parents f4dde8d + 8af4e1d commit ff307a6
Show file tree
Hide file tree
Showing 29 changed files with 382 additions and 350 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ test("changes demographic filter", () => {
fireEvent.click(raceOption);

reactImmediately(() => {
expect(metric.demographicView).toBe("race");
expect(metric.demographicView).toBe("raceOrEthnicity");
expect(menuButton).toHaveTextContent("Race");
});

Expand All @@ -86,7 +86,7 @@ test("changes demographic filter", () => {
const ageOption = screen.getByRole("option", { name: "Age" });
fireEvent.click(ageOption);
reactImmediately(() => {
expect(metric.demographicView).toBe("age");
expect(metric.demographicView).toBe("ageBucket");
expect(menuButton).toHaveTextContent("Age");
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { action } from "mobx";
import { observer } from "mobx-react-lite";
import React from "react";
import HistoricalPopulationBreakdownMetric from "../contentModels/HistoricalPopulationBreakdownMetric";
import { DemographicView, isDemographicView } from "../metricsApi";
import { DemographicView, isDemographicView } from "../demographics";
import { Dropdown } from "../UiLibrary";

type DemographicFilterOption = { id: DemographicView; label: string };
Expand All @@ -33,9 +33,9 @@ const DemographicFilterSelect: React.FC<DemographicFilterSelectProps> = ({
}) => {
const options: DemographicFilterOption[] = [
{ id: "total", label: "Total" },
{ id: "race", label: "Race" },
{ id: "raceOrEthnicity", label: "Race" },
{ id: "gender", label: "Gender" },
{ id: "age", label: "Age" },
{ id: "ageBucket", label: "Age" },
];

const onChange = action("change demographic filter", (newFilter: string) => {
Expand Down
1 change: 1 addition & 0 deletions spotlight-client/src/charts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
// eslint-disable-next-line import/prefer-default-export
export { default as WindowedTimeSeries } from "./WindowedTimeSeries";
export * from "./WindowedTimeSeries";
export * from "./types";
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
// =============================================================================

import { DataSeries } from "../charts/types";
import {
DemographicsByCategoryRecord,
recordIsTotalByDimension,
} from "../metricsApi";
import { recordIsTotalByDimension } from "../demographics";
import { DemographicsByCategoryRecord } from "../metricsApi";
import Metric from "./Metric";

export default class DemographicsByCategoryMetric extends Metric<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@
import { isEqual } from "date-fns";
import { advanceTo, clear } from "jest-date-mock";
import { runInAction } from "mobx";
import {
DEMOGRAPHIC_UNKNOWN,
DIMENSION_DATA_KEYS,
DIMENSION_MAPPINGS,
} from "../demographics";
import { DemographicViewList, getDemographicCategories } from "../demographics";
import {
fetchMetrics,
HistoricalPopulationBreakdownRecord,
Expand Down Expand Up @@ -102,23 +98,22 @@ const getMetric = async () => {
test("fills in missing data", async () => {
const metric = await getMetric();

DIMENSION_MAPPINGS.forEach((categoryLabels, demographicView) => {
DemographicViewList.forEach((demographicView) => {
runInAction(() => {
metric.demographicView = demographicView;
});

reactImmediately(() => {
const data = metric.dataSeries;
if (data) {
Array.from(categoryLabels.keys()).forEach((identifier, index) => {
if (identifier === DEMOGRAPHIC_UNKNOWN) return;
const categories = getDemographicCategories(demographicView);
categories.forEach(({ identifier }, index) => {
const series = data[index].coordinates;
expect(series.length).toBe(240);

const expectedRecordShape = { ...imputedRecordBase };
if (demographicView !== "total") {
const categoryKey = DIMENSION_DATA_KEYS[demographicView];
expectedRecordShape[categoryKey] = identifier;
expectedRecordShape[demographicView] = identifier;
}
series.forEach((record) => {
// separate imputed records from existing records
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@
import { ascending } from "d3-array";
import { eachMonthOfInterval, format, startOfMonth, subMonths } from "date-fns";
import { makeObservable, observable, runInAction } from "mobx";
import { DataSeries } from "../charts/types";
import { DataSeries } from "../charts";
import {
DEMOGRAPHIC_UNKNOWN,
DIMENSION_DATA_KEYS,
DIMENSION_MAPPINGS,
DemographicViewList,
recordIsTotalByDimension,
getDemographicCategories,
RaceIdentifier,
GenderIdentifier,
AgeIdentifier,
} from "../demographics";
import {
AgeIdentifier,
DemographicFields,
GenderIdentifier,
HistoricalPopulationBreakdownRecord,
RaceIdentifier,
recordIsTotalByDimension,
} from "../metricsApi";
import { colors } from "../UiLibrary";
import Metric, { BaseMetricConstructorOptions } from "./Metric";
Expand Down Expand Up @@ -123,45 +122,42 @@ export default class HistoricalPopulationBreakdownMetric extends Metric<
const missingRecords: HistoricalPopulationBreakdownRecord[] = [];

// isolate each data series and impute any missing records
DIMENSION_MAPPINGS.forEach((categoryLabels, demographicView) => {
DemographicViewList.forEach((demographicView) => {
const recordsForDemographicView = transformedData.filter(
recordIsTotalByDimension(demographicView)
);
Array.from(categoryLabels.keys())
// don't need to include unknown in this data;
// they are minimal to nonexistent in historical data and make the legend confusing
.filter((identifier) => identifier !== DEMOGRAPHIC_UNKNOWN)
.forEach((identifier) => {
let recordsForCategory;
if (demographicView !== "total") {
const categoryKey = DIMENSION_DATA_KEYS[demographicView];
recordsForCategory = recordsForDemographicView.filter(
(record) => record[categoryKey] === identifier
);
} else {
recordsForCategory = recordsForDemographicView;
}
missingRecords.push(
...getMissingMonthsForSeries({
records: recordsForCategory,
includeCurrentMonth,
demographicFields: {
raceOrEthnicity:
demographicView === "race"
? (identifier as RaceIdentifier)
: "ALL",
gender:
demographicView === "gender"
? (identifier as GenderIdentifier)
: "ALL",
ageBucket:
demographicView === "age"
? (identifier as AgeIdentifier)
: "ALL",
},
})

const categories = getDemographicCategories(demographicView);
categories.forEach(({ identifier }) => {
let recordsForCategory;
if (demographicView !== "total") {
recordsForCategory = recordsForDemographicView.filter(
(record) => record[demographicView] === identifier
);
});
} else {
recordsForCategory = recordsForDemographicView;
}
missingRecords.push(
...getMissingMonthsForSeries({
records: recordsForCategory,
includeCurrentMonth,
demographicFields: {
raceOrEthnicity:
demographicView === "raceOrEthnicity"
? (identifier as RaceIdentifier)
: "ALL",
gender:
demographicView === "gender"
? (identifier as GenderIdentifier)
: "ALL",
ageBucket:
demographicView === "ageBucket"
? (identifier as AgeIdentifier)
: "ALL",
},
})
);
});
});

transformedData.push(...missingRecords);
Expand All @@ -183,30 +179,17 @@ export default class HistoricalPopulationBreakdownMetric extends Metric<

get dataSeries(): DataSeries<HistoricalPopulationBreakdownRecord>[] | null {
const { records, demographicView } = this;
if (!records || demographicView === "nofilter") return null;

const labelsForDimension = DIMENSION_MAPPINGS.get(demographicView);
// this should never happen, it's really just a type safety measure.
// if it does, something has gone catastrophically wrong
if (!labelsForDimension)
throw new Error("Unsupported demographic view. Unable to provide data.");

return (
Array.from(labelsForDimension)
// don't need to include unknown in this data;
// they are minimal to nonexistent in historical data and make the legend confusing
.filter(([identifier]) => identifier !== DEMOGRAPHIC_UNKNOWN)
.map(([identifier, label], index) => ({
label,
color: colors.dataViz[index],
coordinates:
demographicView === "total"
? records
: records.filter(
(record) =>
record[DIMENSION_DATA_KEYS[demographicView]] === identifier
),
}))
);
if (!records) return null;

const categories = getDemographicCategories(demographicView);

return categories.map(({ identifier, label }, index) => ({
label,
color: colors.dataViz[index],
coordinates:
demographicView === "total"
? records
: records.filter((record) => record[demographicView] === identifier),
}));
}
}
4 changes: 2 additions & 2 deletions spotlight-client/src/contentModels/Metric.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,13 @@ test("demographic filter", async () => {
reactImmediately(() => expect(metric.records).toMatchSnapshot());

runInAction(() => {
metric.demographicView = "race";
metric.demographicView = "raceOrEthnicity";
});

reactImmediately(() => expect(metric.records).toMatchSnapshot());

runInAction(() => {
metric.demographicView = "age";
metric.demographicView = "ageBucket";
});

reactImmediately(() => expect(metric.records).toMatchSnapshot());
Expand Down
2 changes: 1 addition & 1 deletion spotlight-client/src/contentModels/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import {
import { DataSeries } from "../charts/types";
import { ERROR_MESSAGES } from "../constants";
import { LocalityLabels, TenantId } from "../contentApi/types";
import { DemographicView } from "../demographics";
import {
fetchMetrics,
RawMetricData,
DemographicFields,
DemographicView,
LocalityFields,
} from "../metricsApi";
import { MetricRecord, CollectionMap } from "./types";
Expand Down
3 changes: 2 additions & 1 deletion spotlight-client/src/contentModels/RecidivismRateMetric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
// =============================================================================

import { DataSeries } from "../charts/types";
import { RecidivismRateRecord, recordIsTotalByDimension } from "../metricsApi";
import { recordIsTotalByDimension } from "../demographics";
import { RecidivismRateRecord } from "../metricsApi";
import Metric from "./Metric";

export default class RecidivismRateMetric extends Metric<RecidivismRateRecord> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
// =============================================================================

import { DataSeries } from "../charts/types";
import { recordIsTotalByDimension } from "../demographics";
import {
recordIsTotalByDimension,
recordMatchesLocality,
SentenceTypeByLocationRecord,
} from "../metricsApi";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
// =============================================================================

import { DataSeries } from "../charts/types";
import { recordIsTotalByDimension } from "../demographics";
import {
recordIsTotalByDimension,
recordMatchesLocality,
SupervisionSuccessRateDemographicsRecord,
} from "../metricsApi";
Expand Down
11 changes: 6 additions & 5 deletions spotlight-client/src/contentModels/createMetricMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { assertNever } from "assert-never";
import { MetricTypeIdList, TenantContent, TenantId } from "../contentApi/types";
import {
NOFILTER_KEY,
parolePopulationCurrent,
parolePopulationHistorical,
paroleProgramParticipationCurrent,
Expand Down Expand Up @@ -49,8 +50,8 @@ import RecidivismRateMetric from "./RecidivismRateMetric";
import SentenceTypeByLocationMetric from "./SentenceTypeByLocationMetric";
import SupervisionSuccessRateDemographicsMetric from "./SupervisionSuccessRateDemographicsMetric";
import SupervisionSuccessRateMonthlyMetric from "./SupervisionSuccessRateMonthlyMetric";
import { NOFILTER_KEY, TOTAL_KEY } from "../metricsApi/utils";
import { ERROR_MESSAGES } from "../constants";
import { TOTAL_KEY } from "../demographics";

type MetricMappingFactoryOptions = {
localityLabelMapping: TenantContent["localities"];
Expand Down Expand Up @@ -92,7 +93,7 @@ export default function createMetricMapping({
new PopulationBreakdownByLocationMetric({
...metadata,
tenantId,
defaultDemographicView: NOFILTER_KEY,
defaultDemographicView: "total",
defaultLocalityId: TOTAL_KEY,
localityLabels: localityLabelMapping.Sentencing,
dataTransformer: sentencePopulationCurrent,
Expand Down Expand Up @@ -126,7 +127,7 @@ export default function createMetricMapping({
new PopulationBreakdownByLocationMetric({
...metadata,
tenantId,
defaultDemographicView: NOFILTER_KEY,
defaultDemographicView: "total",
defaultLocalityId: TOTAL_KEY,
localityLabels: localityLabelMapping.Prison,
dataTransformer: prisonPopulationCurrent,
Expand All @@ -144,7 +145,7 @@ export default function createMetricMapping({
new PopulationBreakdownByLocationMetric({
...metadata,
tenantId,
defaultDemographicView: NOFILTER_KEY,
defaultDemographicView: "total",
defaultLocalityId: TOTAL_KEY,
localityLabels: localityLabelMapping.Probation,
dataTransformer: probationPopulationCurrent,
Expand All @@ -162,7 +163,7 @@ export default function createMetricMapping({
new PopulationBreakdownByLocationMetric({
...metadata,
tenantId,
defaultDemographicView: NOFILTER_KEY,
defaultDemographicView: "total",
defaultLocalityId: TOTAL_KEY,
localityLabels: localityLabelMapping.Parole,
dataTransformer: parolePopulationCurrent,
Expand Down
Loading

0 comments on commit ff307a6

Please sign in to comment.