diff --git a/app/assets/javascripts/components/BoxAndWhisker.js b/app/assets/javascripts/components/BoxAndWhisker.js index 0e15b4e3d2..d930a9a18f 100644 --- a/app/assets/javascripts/components/BoxAndWhisker.js +++ b/app/assets/javascripts/components/BoxAndWhisker.js @@ -14,7 +14,7 @@ function percentiles(values) { } -export default function BoxAndWhisker({values, boxStyle, whiskerStyle, labelStyle, style}) { +export default function BoxAndWhisker({values, boxStyle, whiskerStyle, labelStyle, showQuartiles, quartileLabelStyle, style}) { const [min, p25, p50, p75, max] = percentiles(values); const labelWidth = 50; // arbitrary @@ -67,6 +67,26 @@ export default function BoxAndWhisker({values, boxStyle, whiskerStyle, labelStyl top: midTop+barHeight/2, color: '#333', ...labelStyle}}>{p50} + {showQuartiles &&
+
{p25}
+
{p75}
+
} ); @@ -76,5 +96,7 @@ BoxAndWhisker.propTypes = { style: PropTypes.object, boxStyle: PropTypes.object, whiskerStyle: PropTypes.object, - labelStyle: PropTypes.object + labelStyle: PropTypes.object, + showQuartiles: PropTypes.bool, + quartileLabelStyle: PropTypes.object }; diff --git a/app/assets/javascripts/components/Histogram.js b/app/assets/javascripts/components/Histogram.js new file mode 100644 index 0000000000..94b7627b32 --- /dev/null +++ b/app/assets/javascripts/components/Histogram.js @@ -0,0 +1,64 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import _ from 'lodash'; + +// Visual component showing a small histogram, initially made for +// percentiles. +export default class Histogram extends React.Component { + scale(count) { + const {values} = this.props; + return `${Math.ceil(100 * count / values.length)}%`; + } + + render() { + const {range, values, bucketSize, height, style = {}} = this.props; + const buckets = _.groupBy(values, value => bucketSize * Math.floor(value / bucketSize)); + const bucketValues = _.range(range[0], range[1], bucketSize); + return ( +
+
+ {bucketValues.map(bucketFloor => { + const countInBucket = (buckets[bucketFloor] || []).length; + return this.renderBucket(bucketFloor, bucketValues.length, countInBucket); + })} +
+
+ ); + } + + + renderBucket(bucketFloor, bucketsCount, countInBucket) { + if (countInBucket === 0) return null; + + const {bucketSize, values, range, height, innerStyle} = this.props; + const width = (range[1] - range[0]) / bucketsCount; + const yScale = this.props.yScale || 1; + const x = (range[1] - range[0]) * (bucketFloor / range[1]); + const y = Math.ceil(height * countInBucket / values.length) / yScale; + const title = `${bucketFloor}-${bucketFloor+bucketSize} has ${countInBucket} values, ${Math.round(100* countInBucket/values.length)}%`; + + return ( +
+
{'\u00A0'}
+
+ ); + } +} + +Histogram.propTypes = { + bucketSize: PropTypes.number.isRequired, + range: PropTypes.arrayOf(PropTypes.number).isRequired, + values: PropTypes.arrayOf(PropTypes.number).isRequired, + height: PropTypes.number.isRequired, + style: PropTypes.object, + innerStyle: PropTypes.object, + yScale: PropTypes.number +}; diff --git a/app/assets/javascripts/reading/readingData.js b/app/assets/javascripts/reading/readingData.js index ad8ff63a69..244d88ced3 100644 --- a/app/assets/javascripts/reading/readingData.js +++ b/app/assets/javascripts/reading/readingData.js @@ -1,9 +1,16 @@ import _ from 'lodash'; +import moment from 'moment'; import { high, medium, low } from '../helpers/colors'; +import { + toSchoolYear, + firstDayOfSchool, + lastDayOfSchool +} from '../helpers/schoolYear'; + export const DIBELS_DORF_WPM = 'dibels_dorf_wpm'; export const DIBELS_DORF_ACC = 'dibels_dorf_acc'; @@ -128,4 +135,21 @@ export function interpretFAndPEnglish(text) { export function orderedFAndPLevels() { return _.sortBy(Object.keys(ORDERED_F_AND_P_ENGLISH_LEVELS), level => ORDERED_F_AND_P_ENGLISH_LEVELS[level]); -} \ No newline at end of file +} + +export function benchmarkPeriodKeyFor(timeMoment) { + const year = toSchoolYear(timeMoment); + const fallStart = firstDayOfSchool(year); + const winterStart = toMoment([year+1, 1, 1]); + const springStart = toMoment([year+1, 5, 1]); + const summerStart = lastDayOfSchool(year); + + if (timeMoment.isBetween(fallStart, winterStart)) return 'fall'; + if (timeMoment.isBetween(winterStart, springStart)) return 'winter'; + if (timeMoment.isBetween(springStart, summerStart)) return 'spring'; + return 'summer'; +} + +function toMoment(triple) { + return moment.utc([triple[0], triple[1], triple[2]].join('-'), 'YYYY-M-D'); +} diff --git a/app/assets/javascripts/reading_debug/GradeTimeGrid.js b/app/assets/javascripts/reading_debug/GradeTimeGrid.js new file mode 100644 index 0000000000..43a4726cf2 --- /dev/null +++ b/app/assets/javascripts/reading_debug/GradeTimeGrid.js @@ -0,0 +1,132 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import {gradeText} from '../helpers/gradeText'; + +// Render a grid of grade by time, with selection. +export default class GradeTimeGrid extends React.Component { + constructor(props) { + super(props); + this.state = { + selection: null + }; + } + + render() { + const {isFlipped} = this.props; + return (isFlipped) + ? this.renderFlipped() + : this.renderNormal(); + } + + renderNormal() { + const {selection, grades, intervals, onSelectionChanged, renderCellFn} = this.props; + + return ( +
+ + + + + {intervals.map(interval => { + const [year, period] = interval; + return ( + + ); + })} + + + + {grades.map(grade => ( + + + {intervals.map(interval => { + const [year, period] = interval; + return ( + + ); + })} + + ))} + +
+
{year}
+
{period}
+
{gradeText(grade)} now +
onSelectionChanged({year, period, grade})}> + {renderCellFn({year, period, grade})} +
+
+
+ ); + } + + renderFlipped() { + const {selection, grades, intervals, onSelectionChanged, renderCellFn} = this.props; + + return ( +
+ + + + + {grades.map(grade => ( + + + ))} + + + + {intervals.map(interval => { + const [year, period] = interval; + return ( + + + {grades.map(grade => { + return ( + + ); + })} + + ); + })} + +
+ {gradeText(grade)} now
{year} {period}
+
onSelectionChanged({year, period, grade})}> + {renderCellFn({year, period, grade})} +
+
+
+ ); + } +} +GradeTimeGrid.propTypes = { + grades: PropTypes.arrayOf(PropTypes.string).isRequired, + intervals: PropTypes.array.isRequired, + renderCellFn: PropTypes.func.isRequired, + selection: PropTypes.object, + onSelectionChanged: PropTypes.func.isRequired, + isFlipped: PropTypes.bool +}; + +const styles = { + firstColumnCell: { + textAlign: 'left' + }, + headCell: { + } +}; diff --git a/app/assets/javascripts/reading_debug/ReadingDebugPage.js b/app/assets/javascripts/reading_debug/ReadingDebugPage.js index 5299b7a2fe..70fdbee99a 100644 --- a/app/assets/javascripts/reading_debug/ReadingDebugPage.js +++ b/app/assets/javascripts/reading_debug/ReadingDebugPage.js @@ -8,7 +8,6 @@ import GenericLoader from '../components/GenericLoader'; import SectionHeading from '../components/SectionHeading'; import ExperimentalBanner from '../components/ExperimentalBanner'; import StudentPhotoCropped from '../components/StudentPhotoCropped'; -import {gradeText} from '../helpers/gradeText'; import {classifyFAndPEnglish, interpretFAndPEnglish} from '../reading/readingData'; import FountasAndPinellBreakdown from '../reading/FountasAndPinellBreakdown'; import { @@ -17,6 +16,8 @@ import { low, missing } from '../helpers/colors'; +import GradeTimeGrid from './GradeTimeGrid'; + // For reviewing, debugging and developing new ways to make use of // or revise reading data. @@ -68,6 +69,7 @@ export class ReadingDebugView extends React.Component { }; this.renderName = this.renderName.bind(this); + this.onCellClicked = this.onCellClicked.bind(this); } onCellClicked(selection) { @@ -118,61 +120,35 @@ export class ReadingDebugView extends React.Component { [2019, 'winter'] ]; const grades = ['KF', '1', '2']; + return ( -
- - - - - {intervals.map(interval => { - const [year, period] = interval; - return ( - - ); - })} - - - - {grades.map(grade => ( - - - {intervals.map(interval => { - const [year, period] = interval; - const key = [year, period, grade].join('-'); - const intervalDataPoints = groups[key] || []; - const fAndPValuesWithNulls = intervalDataPoints.map(dataPoint => dataPoint.json.value); - return ( - - ); - })} - - ))} - -
Grade -
{year}
-
{period}
-
{gradeText(grade)} -
- -
-
-
+ this.renderFAndPCell(groups, cellParams)} + selection={selection} + onSelectionChanged={this.onCellClicked} + /> + ); + } + + renderFAndPCell(groups, {grade, year, period}) { + const key = [year, period, grade].join('-'); + const intervalDataPoints = groups[key] || []; + const fAndPValuesWithNulls = intervalDataPoints.map(dataPoint => dataPoint.json.value); + return ( + ); } + renderTable() { const {students, readingBenchmarkDataPoints} = this.props; const {benchmarkAssessmentKey, selection} = this.state; @@ -355,3 +331,4 @@ function coloredBadge(value, color) { }}>{value} ); } + diff --git a/app/assets/javascripts/reading_debug/ReadingDebugStarPage.js b/app/assets/javascripts/reading_debug/ReadingDebugStarPage.js new file mode 100644 index 0000000000..534e0b4d7e --- /dev/null +++ b/app/assets/javascripts/reading_debug/ReadingDebugStarPage.js @@ -0,0 +1,380 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import {apiFetchJson} from '../helpers/apiFetchJson'; +import memoizer from '../helpers/memoizer'; +import {updateGlobalStylesToTakeFullHeight} from '../helpers/globalStylingWorkarounds'; +import {toSchoolYear} from '../helpers/schoolYear'; +import GenericLoader from '../components/GenericLoader'; +import SectionHeading from '../components/SectionHeading'; +import ExperimentalBanner from '../components/ExperimentalBanner'; +import Histogram from '../components/Histogram'; +import StudentPhotoCropped from '../components/StudentPhotoCropped'; +import BreakdownBar from '../components/BreakdownBar'; +import SimpleFilterSelect from '../components/SimpleFilterSelect'; +import BoxAndWhisker from '../components/BoxAndWhisker'; +import { + classifyFAndPEnglish, + interpretFAndPEnglish, + benchmarkPeriodKeyFor +} from '../reading/readingData'; +import {toMomentFromTimestamp} from '../helpers/toMoment'; +import { + high, + medium, + low, + missing +} from '../helpers/colors'; +import GradeTimeGrid from './GradeTimeGrid'; +import {starBucket} from '../class_lists/studentFilters'; + +// For reviewing, debugging and developing new ways to make use of +// or revise reading data. +export default class ReadingDebugStarPage extends React.Component { + constructor(props) { + super(props); + this.fetchJson = this.fetchJson.bind(this); + this.renderJson = this.renderJson.bind(this); + } + + componentDidMount() { + updateGlobalStylesToTakeFullHeight(); + } + + fetchJson() { + return apiFetchJson('/api/reading/star_reading_debug_json'); + } + + render() { + return ( +
+ + STAR Reading data (DEBUG) + +
+ ); + } + + renderJson(json) { + return ( + + ); + } +} + + +export class ReadingDebugStarView extends React.Component { + constructor(props) { + super(props); + this.state = { + visualization: 'HISTOGRAM', + selection: null, + isFlipped: false, + }; + + this.renderName = this.renderName.bind(this); + this.onCellClicked = this.onCellClicked.bind(this); + this.onVisualizationChange = this.onVisualizationChange.bind(this); + this.onFlippedClicked = this.onFlippedClicked.bind(this); + this.memoize = memoizer(); + } + + groups() { + return this.memoize(['groups', this.state, this.props], () => { + const {students, starReadings} = this.props; + const studentsById = students.reduce((map, student) => { + return {...map, [student.id]: student}; + }, {}); + return _.groupBy(starReadings, result => { + const student = studentsById[result.student_id]; + const dateTakenMoment = toMomentFromTimestamp(result.date_taken); + return [ + toSchoolYear(dateTakenMoment), + benchmarkPeriodKeyFor(dateTakenMoment), + student.grade, + ].join('-'); + }); + }); + } + + onCellClicked(selection) { + this.setState({selection}); + } + + onVisualizationChange(visualization) { + this.setState({visualization}); + } + + onFlippedClicked() { + const {isFlipped} = this.state; + this.setState({isFlipped: !isFlipped}); + } + + render() { + const {isFlipped} = this.state; + return ( +
+
+ {this.renderVisualizationSelect()} +
Flip table
+
+
+ {this.renderList()} +
+
{this.renderTable()}
+
+ ); + } + + renderVisualizationSelect() { + const {visualization} = this.state; + const options = [ + {value: 'HISTOGRAM', label: 'Histogram'}, + {value: 'BOX_AND_WHISKER', label: 'Box and whisker'}, + {value: 'COLOR_BUCKETS', label: 'Color buckets'}, + {value: 'ASSESSMENT_COUNT', label: 'Assessment count'} + ]; + return ( + + ); + } + + renderList() { + return this.memoize(['renderList', this.state, this.props], () => { + const {grades} = this.props; + const {selection, isFlipped} = this.state; + const groups = this.groups(); + + const intervals = [ + [2017, 'winter'], + [2017, 'spring'], + [2018, 'fall'], + [2018, 'winter'], + [2018, 'spring'], + [2019, 'fall'], + [2019, 'winter'] + ]; + + return ( + this.renderStarCell(groups, cellParams)} + selection={selection} + isFlipped={isFlipped} + onSelectionChanged={this.onCellClicked} + /> + ); + }); + } + + renderStarCell(groups, {grade, year, period}) { + const {visualization} = this.state; + const key = [year, period, grade].join('-'); + const starReadings = groups[key] || []; + + // branch! + if (visualization === 'BOX_AND_WHISKER') { + const values = starReadings.map(r => r.percentile_rank); + return (_.compact(values).length === 0) + ? null + : ; + } + + if (visualization === 'COLOR_BUCKETS') { + const counts = _.countBy(starReadings, result => { + return (!result.percentile_rank) + ? 'missing' + : starBucket(result.percentile_rank); + }); + const lowCount = counts.low || 0; + const mediumCount = counts.medium || 0; + const highCount = counts.high || 0; + const missingCount = counts.missing || 0; + + const items = [ + { left: 0, width: missingCount, color: missing, key: 'missing' }, + { left: missingCount, width: highCount, color: high, key: 'high' }, + { left: missingCount + highCount, width: mediumCount, color: medium, key: 'medium' }, + { left: missingCount + highCount + mediumCount, width: lowCount, color: low, key: 'low' } + ]; + return ( + + ); + } + + if (visualization === 'HISTOGRAM') { + return this.renderHistogram(starReadings); + } + + if (visualization === 'ASSESSMENT_COUNT') { + return ( +
+ {starReadings.length} +
+ ); + } + } + + renderHistogram(starReadings) { + const values = starReadings.map(r => r.percentile_rank); + return ( + + ); + } + + renderTable() { + return
Table not added yet.
; + } + + renderName(cellProps) { + const student = cellProps.rowData; + return ( +
+ {student.first_name} {student.last_name} + {student.has_photo && ( + + )} +
+ ); + } + + renderFAndP(dataPointsByStudentId, benchmarkPeriodKey, cellProps) { + const student = cellProps.rowData; + const dataPoints = dataPointsByStudentId[student.id] || []; + if (dataPoints.length === 0) return null; + + return ( +
+ {dataPoints.map(d => ( +
+ {renderFAndPLevel(d.json.value, student.grade, benchmarkPeriodKey)} +
+ ))} +
+ ); + } +} +ReadingDebugStarView.propTypes = { + cutoffMoment: PropTypes.object.isRequired, + grades: PropTypes.arrayOf(PropTypes.string).isRequired, + starReadings: PropTypes.arrayOf(PropTypes.object).isRequired, + students: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number.isRequired, + first_name: PropTypes.string.isRequired, + last_name: PropTypes.string.isRequired, + grade: PropTypes.string.isRequired + })).isRequired, +}; + + +const styles = { + flexVertical: { + display: 'flex', + flex: 1, + flexDirection: 'column' + }, + title: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center' + }, + // Matching react-select + search: { + display: 'inline-block', + padding: '7px 7px 7px 12px', + borderRadius: 4, + border: '1px solid #ccc', + marginLeft: 20, + marginRight: 10, + fontSize: 14, + width: 220 + } +}; + + +function renderFAndPLevel(text, grade, benchmarkPeriodKey) { + if (!text) return none(); + const level = interpretFAndPEnglish(text); + if (!level) return none(); + + // return
{level}
; + const category = classifyFAndPEnglish(level, grade, benchmarkPeriodKey); + const color = { + high, + medium, + low + }[category] || missing; + return ( +
+
+ {coloredBadge(level, color)} +
+
+ ); +} + +function none() { + return (none); +} + + +function coloredBadge(value, color) { + return ( +
{value}
+ ); +} diff --git a/app/assets/javascripts/reading_debug/ReadingDebugStarPage.test.js b/app/assets/javascripts/reading_debug/ReadingDebugStarPage.test.js new file mode 100644 index 0000000000..383ed2a8c8 --- /dev/null +++ b/app/assets/javascripts/reading_debug/ReadingDebugStarPage.test.js @@ -0,0 +1,41 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import fetchMock from 'fetch-mock/es5/client'; +import renderer from 'react-test-renderer'; +import {toMomentFromTimestamp} from '../helpers/toMoment'; +import ReadingDebugStarPage, {ReadingDebugStarView} from './ReadingDebugStarPage'; +import starReadingDebugJson from './star_reading_debug_json.fixture.js'; + +beforeEach(() => { + fetchMock.reset(); + fetchMock.restore(); + fetchMock.get('express:/api/reading/star_reading_debug_json', starReadingDebugJson); +}); + +export function testProps(props = {}) { + return { + ...props + }; +} + +it('renders without crashing', () => { + const el = document.createElement('div'); + const props = testProps(); + ReactDOM.render(, el); +}); + + +it('snapshots view', () => { + const json = starReadingDebugJson; + const props = testProps({ + students: json.students, + cutoffMoment: toMomentFromTimestamp(json.cutoff_date), + grades: json.grades, + schools: json.schools, + starReadings: json.star_readings + }); + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); +}); \ No newline at end of file diff --git a/app/assets/javascripts/reading_debug/__snapshots__/ReadingDebugPage.test.js.snap b/app/assets/javascripts/reading_debug/__snapshots__/ReadingDebugPage.test.js.snap index 0ceb6353a3..e14ff4b2a2 100644 --- a/app/assets/javascripts/reading_debug/__snapshots__/ReadingDebugPage.test.js.snap +++ b/app/assets/javascripts/reading_debug/__snapshots__/ReadingDebugPage.test.js.snap @@ -21,10 +21,16 @@ exports[`snapshots view 1`] = ` > - - Grade - - + +
2017
@@ -32,7 +38,9 @@ exports[`snapshots view 1`] = ` winter - +
2017
@@ -40,7 +48,9 @@ exports[`snapshots view 1`] = ` spring - +
2018
@@ -48,7 +58,9 @@ exports[`snapshots view 1`] = ` fall - +
2018
@@ -56,7 +68,9 @@ exports[`snapshots view 1`] = ` winter - +
2018
@@ -64,7 +78,9 @@ exports[`snapshots view 1`] = ` spring - +
2019
@@ -72,7 +88,9 @@ exports[`snapshots view 1`] = ` fall - +
2019
@@ -84,8 +102,15 @@ exports[`snapshots view 1`] = ` - + Kindergarten + now
- + 1st grade + now
- + 2nd grade + now
+
+
+
+
+
+ + Histogram + +
+
+ +
+ +
+
+
+ + + +
+
+
+ Flip table +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ 2017 +
+
+ winter +
+
+
+ 2017 +
+
+ spring +
+
+
+ 2018 +
+
+ fall +
+
+
+ 2018 +
+
+ winter +
+
+
+ 2018 +
+
+ spring +
+
+
+ 2019 +
+
+ fall +
+
+
+ 2019 +
+
+ winter +
+
+ 3rd grade + now + +
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4th grade + now + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5th grade + now + +
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+   +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 6th grade + now + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 7th grade + now + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8th grade + now + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table not added yet. +
+
+
+`; diff --git a/app/assets/javascripts/reading_debug/star_reading_debug_json.fixture.js b/app/assets/javascripts/reading_debug/star_reading_debug_json.fixture.js new file mode 100644 index 0000000000..29e5144787 --- /dev/null +++ b/app/assets/javascripts/reading_debug/star_reading_debug_json.fixture.js @@ -0,0 +1 @@ +export default {"students":[{"id":2,"grade":"8","first_name":"Ryan","last_name":"Rodriguez","school_id":6},{"id":14,"grade":"5","first_name":"Pocahontas","last_name":"Pan","school_id":2},{"id":15,"grade":"5","first_name":"Pocahontas","last_name":"Disney","school_id":2},{"id":17,"grade":"5","first_name":"Minnie","last_name":"White","school_id":2},{"id":18,"grade":"5","first_name":"Mickey","last_name":"Skywalker","school_id":2},{"id":19,"grade":"5","first_name":"Minnie","last_name":"Duck","school_id":2},{"id":20,"grade":"5","first_name":"Elsa","last_name":"White","school_id":2},{"id":47,"grade":"5","first_name":"Olaf","last_name":"White","school_id":6},{"id":48,"grade":"5","first_name":"Chip","last_name":"White","school_id":6},{"id":49,"grade":"5","first_name":"Aladdin","last_name":"Disney","school_id":6},{"id":51,"grade":"5","first_name":"Aladdin","last_name":"Duck","school_id":6},{"id":52,"grade":"5","first_name":"Elsa","last_name":"Duck","school_id":6},{"id":53,"grade":"5","first_name":"Chip","last_name":"Disney","school_id":6},{"id":54,"grade":"3","first_name":"Mickey","last_name":"Kenobi","school_id":2},{"id":57,"grade":"3","first_name":"Olaf","last_name":"Disney","school_id":2},{"id":59,"grade":"3","first_name":"Donald","last_name":"White","school_id":2},{"id":60,"grade":"3","first_name":"Mowgli","last_name":"Kenobi","school_id":2},{"id":61,"grade":"3","first_name":"Aladdin","last_name":"Disney","school_id":2},{"id":62,"grade":"3","first_name":"Winnie","last_name":"Skywalker","school_id":2},{"id":16,"grade":"5","first_name":"Pocahontas","last_name":"Skywalker","school_id":2},{"id":21,"grade":"5","first_name":"Aladdin","last_name":"Pan","school_id":2},{"id":46,"grade":"5","first_name":"Pocahontas","last_name":"White","school_id":6},{"id":50,"grade":"5","first_name":"Daisy","last_name":"Disney","school_id":6},{"id":55,"grade":"3","first_name":"Rapunzel","last_name":"White","school_id":2},{"id":58,"grade":"3","first_name":"Pluto","last_name":"Kenobi","school_id":2}],"star_readings":[{"id":171,"percentile_rank":41,"total_time":1745,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":14,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:01:56.946Z","updated_at":"2019-05-09T13:01:56.946Z"},{"id":170,"percentile_rank":15,"total_time":1098,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":14,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:01:56.939Z","updated_at":"2019-05-09T13:01:56.939Z"},{"id":169,"percentile_rank":73,"total_time":1276,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":14,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:01:56.931Z","updated_at":"2019-05-09T13:01:56.931Z"},{"id":168,"percentile_rank":60,"total_time":1721,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":14,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:01:56.924Z","updated_at":"2019-05-09T13:01:56.924Z"},{"id":167,"percentile_rank":92,"total_time":1784,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":14,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:01:56.917Z","updated_at":"2019-05-09T13:01:56.917Z"},{"id":166,"percentile_rank":73,"total_time":1322,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":14,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:01:56.909Z","updated_at":"2019-05-09T13:01:56.909Z"},{"id":165,"percentile_rank":88,"total_time":1002,"grade_equivalent":"4.00","instructional_reading_level":"5.0","student_id":14,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:01:56.902Z","updated_at":"2019-05-09T13:01:56.902Z"},{"id":190,"percentile_rank":70,"total_time":1274,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":15,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:01:57.653Z","updated_at":"2019-05-09T13:01:57.653Z"},{"id":189,"percentile_rank":76,"total_time":1563,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":15,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:01:57.646Z","updated_at":"2019-05-09T13:01:57.646Z"},{"id":188,"percentile_rank":79,"total_time":1349,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":15,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:01:57.640Z","updated_at":"2019-05-09T13:01:57.640Z"},{"id":187,"percentile_rank":68,"total_time":1662,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":15,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:01:57.633Z","updated_at":"2019-05-09T13:01:57.633Z"},{"id":186,"percentile_rank":21,"total_time":1634,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":15,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:01:57.626Z","updated_at":"2019-05-09T13:01:57.626Z"},{"id":185,"percentile_rank":65,"total_time":1733,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":15,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:01:57.619Z","updated_at":"2019-05-09T13:01:57.619Z"},{"id":184,"percentile_rank":18,"total_time":1788,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":15,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:01:57.612Z","updated_at":"2019-05-09T13:01:57.612Z"},{"id":209,"percentile_rank":87,"total_time":1171,"grade_equivalent":"2.60","instructional_reading_level":"5.0","student_id":16,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:01:58.149Z","updated_at":"2019-05-09T13:01:58.149Z"},{"id":208,"percentile_rank":38,"total_time":1014,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":16,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:01:58.142Z","updated_at":"2019-05-09T13:01:58.142Z"},{"id":207,"percentile_rank":92,"total_time":1465,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":16,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:01:58.134Z","updated_at":"2019-05-09T13:01:58.134Z"},{"id":206,"percentile_rank":64,"total_time":1410,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":16,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:01:58.127Z","updated_at":"2019-05-09T13:01:58.127Z"},{"id":205,"percentile_rank":15,"total_time":1591,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":16,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:01:58.121Z","updated_at":"2019-05-09T13:01:58.121Z"},{"id":204,"percentile_rank":96,"total_time":1778,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":16,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:01:58.114Z","updated_at":"2019-05-09T13:01:58.114Z"},{"id":203,"percentile_rank":88,"total_time":1579,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":16,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:01:58.107Z","updated_at":"2019-05-09T13:01:58.107Z"},{"id":228,"percentile_rank":76,"total_time":1436,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":17,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:01:58.650Z","updated_at":"2019-05-09T13:01:58.650Z"},{"id":227,"percentile_rank":20,"total_time":1261,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":17,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:01:58.643Z","updated_at":"2019-05-09T13:01:58.643Z"},{"id":226,"percentile_rank":39,"total_time":1528,"grade_equivalent":"2.60","instructional_reading_level":"6.0","student_id":17,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:01:58.636Z","updated_at":"2019-05-09T13:01:58.636Z"},{"id":225,"percentile_rank":31,"total_time":1627,"grade_equivalent":"2.60","instructional_reading_level":"6.0","student_id":17,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:01:58.629Z","updated_at":"2019-05-09T13:01:58.629Z"},{"id":224,"percentile_rank":80,"total_time":1662,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":17,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:01:58.622Z","updated_at":"2019-05-09T13:01:58.622Z"},{"id":223,"percentile_rank":40,"total_time":1011,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":17,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:01:58.614Z","updated_at":"2019-05-09T13:01:58.614Z"},{"id":222,"percentile_rank":89,"total_time":1030,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":17,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:01:58.604Z","updated_at":"2019-05-09T13:01:58.604Z"},{"id":247,"percentile_rank":58,"total_time":1225,"grade_equivalent":"2.60","instructional_reading_level":"5.0","student_id":18,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:01:59.220Z","updated_at":"2019-05-09T13:01:59.220Z"},{"id":246,"percentile_rank":12,"total_time":1693,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":18,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:01:59.211Z","updated_at":"2019-05-09T13:01:59.211Z"},{"id":245,"percentile_rank":81,"total_time":1426,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":18,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:01:59.199Z","updated_at":"2019-05-09T13:01:59.199Z"},{"id":244,"percentile_rank":41,"total_time":1487,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":18,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:01:59.188Z","updated_at":"2019-05-09T13:01:59.188Z"},{"id":243,"percentile_rank":60,"total_time":1223,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":18,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:01:59.181Z","updated_at":"2019-05-09T13:01:59.181Z"},{"id":242,"percentile_rank":53,"total_time":1469,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":18,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:01:59.170Z","updated_at":"2019-05-09T13:01:59.170Z"},{"id":241,"percentile_rank":86,"total_time":1108,"grade_equivalent":"2.60","instructional_reading_level":"5.0","student_id":18,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:01:59.163Z","updated_at":"2019-05-09T13:01:59.163Z"},{"id":266,"percentile_rank":71,"total_time":1230,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":19,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:00.411Z","updated_at":"2019-05-09T13:02:00.411Z"},{"id":265,"percentile_rank":98,"total_time":1750,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":19,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:00.404Z","updated_at":"2019-05-09T13:02:00.404Z"},{"id":264,"percentile_rank":88,"total_time":1761,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":19,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:00.397Z","updated_at":"2019-05-09T13:02:00.397Z"},{"id":263,"percentile_rank":43,"total_time":1001,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":19,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:00.391Z","updated_at":"2019-05-09T13:02:00.391Z"},{"id":262,"percentile_rank":86,"total_time":1200,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":19,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:00.384Z","updated_at":"2019-05-09T13:02:00.384Z"},{"id":261,"percentile_rank":89,"total_time":1135,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":19,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:00.377Z","updated_at":"2019-05-09T13:02:00.377Z"},{"id":260,"percentile_rank":81,"total_time":1725,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":19,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:00.371Z","updated_at":"2019-05-09T13:02:00.371Z"},{"id":285,"percentile_rank":41,"total_time":1742,"grade_equivalent":"4.00","instructional_reading_level":"5.0","student_id":20,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:00.904Z","updated_at":"2019-05-09T13:02:00.904Z"},{"id":284,"percentile_rank":17,"total_time":1626,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":20,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:00.897Z","updated_at":"2019-05-09T13:02:00.897Z"},{"id":283,"percentile_rank":38,"total_time":1783,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":20,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:00.891Z","updated_at":"2019-05-09T13:02:00.891Z"},{"id":282,"percentile_rank":47,"total_time":1201,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":20,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:00.884Z","updated_at":"2019-05-09T13:02:00.884Z"},{"id":281,"percentile_rank":79,"total_time":1019,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":20,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:00.877Z","updated_at":"2019-05-09T13:02:00.877Z"},{"id":280,"percentile_rank":50,"total_time":1504,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":20,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:00.871Z","updated_at":"2019-05-09T13:02:00.871Z"},{"id":279,"percentile_rank":15,"total_time":1237,"grade_equivalent":"2.60","instructional_reading_level":"6.0","student_id":20,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:00.864Z","updated_at":"2019-05-09T13:02:00.864Z"},{"id":304,"percentile_rank":15,"total_time":1217,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":21,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:01.344Z","updated_at":"2019-05-09T13:02:01.344Z"},{"id":303,"percentile_rank":43,"total_time":1423,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":21,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:01.337Z","updated_at":"2019-05-09T13:02:01.337Z"},{"id":302,"percentile_rank":48,"total_time":1272,"grade_equivalent":"2.60","instructional_reading_level":"5.0","student_id":21,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:01.329Z","updated_at":"2019-05-09T13:02:01.329Z"},{"id":301,"percentile_rank":90,"total_time":1217,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":21,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:01.321Z","updated_at":"2019-05-09T13:02:01.321Z"},{"id":300,"percentile_rank":47,"total_time":1681,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":21,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:01.315Z","updated_at":"2019-05-09T13:02:01.315Z"},{"id":299,"percentile_rank":26,"total_time":1153,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":21,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:01.308Z","updated_at":"2019-05-09T13:02:01.308Z"},{"id":298,"percentile_rank":88,"total_time":1696,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":21,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:01.302Z","updated_at":"2019-05-09T13:02:01.302Z"},{"id":760,"percentile_rank":52,"total_time":1255,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":46,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:13.443Z","updated_at":"2019-05-09T13:02:13.443Z"},{"id":759,"percentile_rank":34,"total_time":1614,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":46,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:13.393Z","updated_at":"2019-05-09T13:02:13.393Z"},{"id":758,"percentile_rank":63,"total_time":1647,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":46,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:13.383Z","updated_at":"2019-05-09T13:02:13.383Z"},{"id":757,"percentile_rank":31,"total_time":1106,"grade_equivalent":"2.60","instructional_reading_level":"5.0","student_id":46,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:13.375Z","updated_at":"2019-05-09T13:02:13.375Z"},{"id":756,"percentile_rank":60,"total_time":1491,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":46,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:13.368Z","updated_at":"2019-05-09T13:02:13.368Z"},{"id":755,"percentile_rank":33,"total_time":1722,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":46,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:13.362Z","updated_at":"2019-05-09T13:02:13.362Z"},{"id":754,"percentile_rank":33,"total_time":1263,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":46,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:13.355Z","updated_at":"2019-05-09T13:02:13.355Z"},{"id":779,"percentile_rank":54,"total_time":1411,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":47,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:14.007Z","updated_at":"2019-05-09T13:02:14.007Z"},{"id":778,"percentile_rank":65,"total_time":1575,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":47,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:13.997Z","updated_at":"2019-05-09T13:02:13.997Z"},{"id":777,"percentile_rank":77,"total_time":1229,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":47,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:13.990Z","updated_at":"2019-05-09T13:02:13.990Z"},{"id":776,"percentile_rank":77,"total_time":1181,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":47,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:13.982Z","updated_at":"2019-05-09T13:02:13.982Z"},{"id":775,"percentile_rank":70,"total_time":1090,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":47,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:13.974Z","updated_at":"2019-05-09T13:02:13.974Z"},{"id":774,"percentile_rank":27,"total_time":1285,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":47,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:13.966Z","updated_at":"2019-05-09T13:02:13.966Z"},{"id":773,"percentile_rank":15,"total_time":1431,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":47,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:13.958Z","updated_at":"2019-05-09T13:02:13.958Z"},{"id":798,"percentile_rank":39,"total_time":1768,"grade_equivalent":"4.00","instructional_reading_level":"5.0","student_id":48,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:14.494Z","updated_at":"2019-05-09T13:02:14.494Z"},{"id":797,"percentile_rank":44,"total_time":1278,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":48,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:14.487Z","updated_at":"2019-05-09T13:02:14.487Z"},{"id":796,"percentile_rank":82,"total_time":1707,"grade_equivalent":"2.60","instructional_reading_level":"5.0","student_id":48,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:14.480Z","updated_at":"2019-05-09T13:02:14.480Z"},{"id":795,"percentile_rank":13,"total_time":1076,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":48,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:14.474Z","updated_at":"2019-05-09T13:02:14.474Z"},{"id":794,"percentile_rank":33,"total_time":1003,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":48,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:14.467Z","updated_at":"2019-05-09T13:02:14.467Z"},{"id":793,"percentile_rank":69,"total_time":1616,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":48,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:14.460Z","updated_at":"2019-05-09T13:02:14.460Z"},{"id":792,"percentile_rank":55,"total_time":1375,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":48,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:14.453Z","updated_at":"2019-05-09T13:02:14.453Z"},{"id":817,"percentile_rank":73,"total_time":1393,"grade_equivalent":"2.60","instructional_reading_level":"6.0","student_id":49,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:14.939Z","updated_at":"2019-05-09T13:02:14.939Z"},{"id":816,"percentile_rank":72,"total_time":1007,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":49,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:14.929Z","updated_at":"2019-05-09T13:02:14.929Z"},{"id":815,"percentile_rank":38,"total_time":1153,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":49,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:14.922Z","updated_at":"2019-05-09T13:02:14.922Z"},{"id":814,"percentile_rank":21,"total_time":1016,"grade_equivalent":"2.60","instructional_reading_level":"6.0","student_id":49,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:14.915Z","updated_at":"2019-05-09T13:02:14.915Z"},{"id":813,"percentile_rank":69,"total_time":1229,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":49,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:14.908Z","updated_at":"2019-05-09T13:02:14.908Z"},{"id":812,"percentile_rank":54,"total_time":1746,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":49,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:14.901Z","updated_at":"2019-05-09T13:02:14.901Z"},{"id":811,"percentile_rank":42,"total_time":1184,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":49,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:14.894Z","updated_at":"2019-05-09T13:02:14.894Z"},{"id":836,"percentile_rank":63,"total_time":1408,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":50,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:15.402Z","updated_at":"2019-05-09T13:02:15.402Z"},{"id":835,"percentile_rank":97,"total_time":1525,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":50,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:15.395Z","updated_at":"2019-05-09T13:02:15.395Z"},{"id":834,"percentile_rank":13,"total_time":1095,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":50,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:15.388Z","updated_at":"2019-05-09T13:02:15.388Z"},{"id":833,"percentile_rank":30,"total_time":1055,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":50,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:15.381Z","updated_at":"2019-05-09T13:02:15.381Z"},{"id":832,"percentile_rank":76,"total_time":1145,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":50,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:15.374Z","updated_at":"2019-05-09T13:02:15.374Z"},{"id":831,"percentile_rank":26,"total_time":1018,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":50,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:15.367Z","updated_at":"2019-05-09T13:02:15.367Z"},{"id":830,"percentile_rank":36,"total_time":1215,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":50,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:15.360Z","updated_at":"2019-05-09T13:02:15.360Z"},{"id":855,"percentile_rank":79,"total_time":1598,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":51,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:15.908Z","updated_at":"2019-05-09T13:02:15.908Z"},{"id":854,"percentile_rank":90,"total_time":1006,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":51,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:15.902Z","updated_at":"2019-05-09T13:02:15.902Z"},{"id":853,"percentile_rank":72,"total_time":1432,"grade_equivalent":"4.00","instructional_reading_level":"5.0","student_id":51,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:15.893Z","updated_at":"2019-05-09T13:02:15.893Z"},{"id":852,"percentile_rank":72,"total_time":1785,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":51,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:15.887Z","updated_at":"2019-05-09T13:02:15.887Z"},{"id":851,"percentile_rank":47,"total_time":1174,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":51,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:15.880Z","updated_at":"2019-05-09T13:02:15.880Z"},{"id":850,"percentile_rank":89,"total_time":1244,"grade_equivalent":"2.60","instructional_reading_level":"5.0","student_id":51,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:15.874Z","updated_at":"2019-05-09T13:02:15.874Z"},{"id":849,"percentile_rank":42,"total_time":1543,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":51,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:15.867Z","updated_at":"2019-05-09T13:02:15.867Z"},{"id":874,"percentile_rank":44,"total_time":1762,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":52,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:16.289Z","updated_at":"2019-05-09T13:02:16.289Z"},{"id":873,"percentile_rank":12,"total_time":1279,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":52,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:16.282Z","updated_at":"2019-05-09T13:02:16.282Z"},{"id":872,"percentile_rank":93,"total_time":1730,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":52,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:16.274Z","updated_at":"2019-05-09T13:02:16.274Z"},{"id":871,"percentile_rank":10,"total_time":1254,"grade_equivalent":"0.00","instructional_reading_level":"5.0","student_id":52,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:16.264Z","updated_at":"2019-05-09T13:02:16.264Z"},{"id":870,"percentile_rank":28,"total_time":1270,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":52,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:16.252Z","updated_at":"2019-05-09T13:02:16.252Z"},{"id":869,"percentile_rank":67,"total_time":1194,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":52,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:16.244Z","updated_at":"2019-05-09T13:02:16.244Z"},{"id":868,"percentile_rank":58,"total_time":1611,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":52,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:16.237Z","updated_at":"2019-05-09T13:02:16.237Z"},{"id":893,"percentile_rank":17,"total_time":1445,"grade_equivalent":"0.00","instructional_reading_level":"6.0","student_id":53,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:16.818Z","updated_at":"2019-05-09T13:02:16.818Z"},{"id":892,"percentile_rank":18,"total_time":1283,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":53,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:16.809Z","updated_at":"2019-05-09T13:02:16.809Z"},{"id":891,"percentile_rank":69,"total_time":1117,"grade_equivalent":"4.00","instructional_reading_level":"6.0","student_id":53,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:16.801Z","updated_at":"2019-05-09T13:02:16.801Z"},{"id":890,"percentile_rank":55,"total_time":1241,"grade_equivalent":"5.70","instructional_reading_level":"6.0","student_id":53,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:16.794Z","updated_at":"2019-05-09T13:02:16.794Z"},{"id":889,"percentile_rank":62,"total_time":1225,"grade_equivalent":"2.60","instructional_reading_level":"6.0","student_id":53,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:16.786Z","updated_at":"2019-05-09T13:02:16.786Z"},{"id":888,"percentile_rank":27,"total_time":1626,"grade_equivalent":"5.70","instructional_reading_level":"5.0","student_id":53,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:16.749Z","updated_at":"2019-05-09T13:02:16.749Z"},{"id":887,"percentile_rank":18,"total_time":1304,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":53,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:16.740Z","updated_at":"2019-05-09T13:02:16.740Z"},{"id":912,"percentile_rank":78,"total_time":1572,"grade_equivalent":"5.70","instructional_reading_level":"2.0","student_id":54,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:17.536Z","updated_at":"2019-05-09T13:02:17.536Z"},{"id":911,"percentile_rank":64,"total_time":1535,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":54,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:17.527Z","updated_at":"2019-05-09T13:02:17.527Z"},{"id":910,"percentile_rank":73,"total_time":1405,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":54,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:17.516Z","updated_at":"2019-05-09T13:02:17.516Z"},{"id":909,"percentile_rank":64,"total_time":1417,"grade_equivalent":"5.70","instructional_reading_level":"3.0","student_id":54,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:17.506Z","updated_at":"2019-05-09T13:02:17.506Z"},{"id":908,"percentile_rank":78,"total_time":1138,"grade_equivalent":"2.60","instructional_reading_level":"3.0","student_id":54,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:17.495Z","updated_at":"2019-05-09T13:02:17.495Z"},{"id":907,"percentile_rank":17,"total_time":1383,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":54,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:17.484Z","updated_at":"2019-05-09T13:02:17.484Z"},{"id":906,"percentile_rank":63,"total_time":1415,"grade_equivalent":"2.60","instructional_reading_level":"3.0","student_id":54,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:17.465Z","updated_at":"2019-05-09T13:02:17.465Z"},{"id":931,"percentile_rank":39,"total_time":1052,"grade_equivalent":"0.00","instructional_reading_level":"2.0","student_id":55,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:18.011Z","updated_at":"2019-05-09T13:02:18.011Z"},{"id":930,"percentile_rank":28,"total_time":1400,"grade_equivalent":"0.00","instructional_reading_level":"3.0","student_id":55,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:18.004Z","updated_at":"2019-05-09T13:02:18.004Z"},{"id":929,"percentile_rank":55,"total_time":1372,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":55,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:17.998Z","updated_at":"2019-05-09T13:02:17.998Z"},{"id":928,"percentile_rank":20,"total_time":1178,"grade_equivalent":"4.00","instructional_reading_level":"3.0","student_id":55,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:17.989Z","updated_at":"2019-05-09T13:02:17.989Z"},{"id":927,"percentile_rank":77,"total_time":1454,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":55,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:17.979Z","updated_at":"2019-05-09T13:02:17.979Z"},{"id":926,"percentile_rank":28,"total_time":1311,"grade_equivalent":"2.60","instructional_reading_level":"3.0","student_id":55,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:17.972Z","updated_at":"2019-05-09T13:02:17.972Z"},{"id":925,"percentile_rank":94,"total_time":1103,"grade_equivalent":"5.70","instructional_reading_level":"3.0","student_id":55,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:17.966Z","updated_at":"2019-05-09T13:02:17.966Z"},{"id":969,"percentile_rank":51,"total_time":1475,"grade_equivalent":"0.00","instructional_reading_level":"3.0","student_id":57,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:18.925Z","updated_at":"2019-05-09T13:02:18.925Z"},{"id":968,"percentile_rank":30,"total_time":1468,"grade_equivalent":"5.70","instructional_reading_level":"3.0","student_id":57,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:18.918Z","updated_at":"2019-05-09T13:02:18.918Z"},{"id":967,"percentile_rank":56,"total_time":1143,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":57,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:18.910Z","updated_at":"2019-05-09T13:02:18.910Z"},{"id":966,"percentile_rank":33,"total_time":1144,"grade_equivalent":"5.70","instructional_reading_level":"2.0","student_id":57,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:18.901Z","updated_at":"2019-05-09T13:02:18.901Z"},{"id":965,"percentile_rank":60,"total_time":1763,"grade_equivalent":"5.70","instructional_reading_level":"3.0","student_id":57,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:18.893Z","updated_at":"2019-05-09T13:02:18.893Z"},{"id":964,"percentile_rank":67,"total_time":1418,"grade_equivalent":"2.60","instructional_reading_level":"2.0","student_id":57,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:18.885Z","updated_at":"2019-05-09T13:02:18.885Z"},{"id":963,"percentile_rank":47,"total_time":1132,"grade_equivalent":"4.00","instructional_reading_level":"2.0","student_id":57,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:18.878Z","updated_at":"2019-05-09T13:02:18.878Z"},{"id":988,"percentile_rank":12,"total_time":1004,"grade_equivalent":"4.00","instructional_reading_level":"2.0","student_id":58,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:19.368Z","updated_at":"2019-05-09T13:02:19.368Z"},{"id":987,"percentile_rank":27,"total_time":1249,"grade_equivalent":"5.70","instructional_reading_level":"2.0","student_id":58,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:19.360Z","updated_at":"2019-05-09T13:02:19.360Z"},{"id":986,"percentile_rank":56,"total_time":1164,"grade_equivalent":"4.00","instructional_reading_level":"2.0","student_id":58,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:19.353Z","updated_at":"2019-05-09T13:02:19.353Z"},{"id":985,"percentile_rank":22,"total_time":1260,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":58,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:19.346Z","updated_at":"2019-05-09T13:02:19.346Z"},{"id":984,"percentile_rank":32,"total_time":1188,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":58,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:19.339Z","updated_at":"2019-05-09T13:02:19.339Z"},{"id":983,"percentile_rank":44,"total_time":1262,"grade_equivalent":"0.00","instructional_reading_level":"2.0","student_id":58,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:19.332Z","updated_at":"2019-05-09T13:02:19.332Z"},{"id":982,"percentile_rank":78,"total_time":1443,"grade_equivalent":"0.00","instructional_reading_level":"3.0","student_id":58,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:19.318Z","updated_at":"2019-05-09T13:02:19.318Z"},{"id":1007,"percentile_rank":97,"total_time":1114,"grade_equivalent":"5.70","instructional_reading_level":"3.0","student_id":59,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:19.944Z","updated_at":"2019-05-09T13:02:19.944Z"},{"id":1006,"percentile_rank":67,"total_time":1120,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":59,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:19.935Z","updated_at":"2019-05-09T13:02:19.935Z"},{"id":1005,"percentile_rank":84,"total_time":1706,"grade_equivalent":"2.60","instructional_reading_level":"4.0","student_id":59,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:19.923Z","updated_at":"2019-05-09T13:02:19.923Z"},{"id":1004,"percentile_rank":18,"total_time":1293,"grade_equivalent":"2.60","instructional_reading_level":"2.0","student_id":59,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:19.915Z","updated_at":"2019-05-09T13:02:19.915Z"},{"id":1003,"percentile_rank":30,"total_time":1060,"grade_equivalent":"4.00","instructional_reading_level":"2.0","student_id":59,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:19.906Z","updated_at":"2019-05-09T13:02:19.906Z"},{"id":1002,"percentile_rank":79,"total_time":1589,"grade_equivalent":"5.70","instructional_reading_level":"2.0","student_id":59,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:19.899Z","updated_at":"2019-05-09T13:02:19.899Z"},{"id":1001,"percentile_rank":15,"total_time":1504,"grade_equivalent":"0.00","instructional_reading_level":"2.0","student_id":59,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:19.888Z","updated_at":"2019-05-09T13:02:19.888Z"},{"id":1026,"percentile_rank":85,"total_time":1641,"grade_equivalent":"0.00","instructional_reading_level":"2.0","student_id":60,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:20.454Z","updated_at":"2019-05-09T13:02:20.454Z"},{"id":1025,"percentile_rank":60,"total_time":1499,"grade_equivalent":"4.00","instructional_reading_level":"3.0","student_id":60,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:20.447Z","updated_at":"2019-05-09T13:02:20.447Z"},{"id":1024,"percentile_rank":58,"total_time":1672,"grade_equivalent":"2.60","instructional_reading_level":"3.0","student_id":60,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:20.437Z","updated_at":"2019-05-09T13:02:20.437Z"},{"id":1023,"percentile_rank":73,"total_time":1000,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":60,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:20.428Z","updated_at":"2019-05-09T13:02:20.428Z"},{"id":1022,"percentile_rank":19,"total_time":1325,"grade_equivalent":"4.00","instructional_reading_level":"3.0","student_id":60,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:20.420Z","updated_at":"2019-05-09T13:02:20.420Z"},{"id":1021,"percentile_rank":68,"total_time":1412,"grade_equivalent":"5.70","instructional_reading_level":"2.0","student_id":60,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:20.411Z","updated_at":"2019-05-09T13:02:20.411Z"},{"id":1020,"percentile_rank":24,"total_time":1712,"grade_equivalent":"0.00","instructional_reading_level":"2.0","student_id":60,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:20.403Z","updated_at":"2019-05-09T13:02:20.403Z"},{"id":1045,"percentile_rank":69,"total_time":1568,"grade_equivalent":"5.70","instructional_reading_level":"3.0","student_id":61,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:20.995Z","updated_at":"2019-05-09T13:02:20.995Z"},{"id":1044,"percentile_rank":79,"total_time":1014,"grade_equivalent":"4.00","instructional_reading_level":"4.0","student_id":61,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:20.988Z","updated_at":"2019-05-09T13:02:20.988Z"},{"id":1043,"percentile_rank":30,"total_time":1717,"grade_equivalent":"5.70","instructional_reading_level":"2.0","student_id":61,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:20.981Z","updated_at":"2019-05-09T13:02:20.981Z"},{"id":1042,"percentile_rank":50,"total_time":1096,"grade_equivalent":"5.70","instructional_reading_level":"3.0","student_id":61,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:20.974Z","updated_at":"2019-05-09T13:02:20.974Z"},{"id":1041,"percentile_rank":22,"total_time":1618,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":61,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:20.968Z","updated_at":"2019-05-09T13:02:20.968Z"},{"id":1040,"percentile_rank":19,"total_time":1542,"grade_equivalent":"4.00","instructional_reading_level":"2.0","student_id":61,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:20.961Z","updated_at":"2019-05-09T13:02:20.961Z"},{"id":1039,"percentile_rank":76,"total_time":1494,"grade_equivalent":"5.70","instructional_reading_level":"4.0","student_id":61,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:20.954Z","updated_at":"2019-05-09T13:02:20.954Z"},{"id":1064,"percentile_rank":44,"total_time":1482,"grade_equivalent":"0.00","instructional_reading_level":"3.0","student_id":62,"date_taken":"2019-02-07T00:00:00.000Z","created_at":"2019-05-09T13:02:21.578Z","updated_at":"2019-05-09T13:02:21.578Z"},{"id":1063,"percentile_rank":60,"total_time":1698,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":62,"date_taken":"2018-11-09T00:00:00.000Z","created_at":"2019-05-09T13:02:21.566Z","updated_at":"2019-05-09T13:02:21.566Z"},{"id":1062,"percentile_rank":36,"total_time":1025,"grade_equivalent":"4.00","instructional_reading_level":"3.0","student_id":62,"date_taken":"2018-08-11T00:00:00.000Z","created_at":"2019-05-09T13:02:21.555Z","updated_at":"2019-05-09T13:02:21.555Z"},{"id":1061,"percentile_rank":45,"total_time":1090,"grade_equivalent":"2.60","instructional_reading_level":"3.0","student_id":62,"date_taken":"2018-05-13T00:00:00.000Z","created_at":"2019-05-09T13:02:21.547Z","updated_at":"2019-05-09T13:02:21.547Z"},{"id":1060,"percentile_rank":91,"total_time":1394,"grade_equivalent":"4.00","instructional_reading_level":"3.0","student_id":62,"date_taken":"2018-02-12T00:00:00.000Z","created_at":"2019-05-09T13:02:21.539Z","updated_at":"2019-05-09T13:02:21.539Z"},{"id":1059,"percentile_rank":30,"total_time":1701,"grade_equivalent":"2.60","instructional_reading_level":"3.0","student_id":62,"date_taken":"2017-11-14T00:00:00.000Z","created_at":"2019-05-09T13:02:21.530Z","updated_at":"2019-05-09T13:02:21.530Z"},{"id":1058,"percentile_rank":10,"total_time":1244,"grade_equivalent":"0.00","instructional_reading_level":"4.0","student_id":62,"date_taken":"2017-08-16T00:00:00.000Z","created_at":"2019-05-09T13:02:21.522Z","updated_at":"2019-05-09T13:02:21.522Z"}],"cutoff_date":"2017-08-15T00:00:00.000+00:00","schools":[{"id":1,"school_type":"ES","name":"Benjamin G Brown","created_at":"2019-05-09T13:01:47.944Z","updated_at":"2019-05-09T13:01:47.944Z","local_id":"BRN","slug":"brn"},{"id":2,"school_type":"ESMS","name":"Arthur D Healey","created_at":"2019-05-09T13:01:47.950Z","updated_at":"2019-05-09T13:01:47.950Z","local_id":"HEA","slug":"hea"},{"id":3,"school_type":"ESMS","name":"John F Kennedy","created_at":"2019-05-09T13:01:47.957Z","updated_at":"2019-05-09T13:01:47.957Z","local_id":"KDY","slug":"kdy"},{"id":4,"school_type":"ESMS","name":"Albert F. Argenziano School","created_at":"2019-05-09T13:01:47.963Z","updated_at":"2019-05-09T13:01:47.963Z","local_id":"AFAS","slug":"afas"},{"id":5,"school_type":"ESMS","name":"E Somerville Community","created_at":"2019-05-09T13:01:47.968Z","updated_at":"2019-05-09T13:01:47.968Z","local_id":"ESCS","slug":"escs"},{"id":6,"school_type":"ESMS","name":"West Somerville Neighborhood","created_at":"2019-05-09T13:01:47.973Z","updated_at":"2019-05-09T13:01:47.973Z","local_id":"WSNS","slug":"wsns"},{"id":7,"school_type":"ESMS","name":"Winter Hill Community","created_at":"2019-05-09T13:01:47.978Z","updated_at":"2019-05-09T13:01:47.978Z","local_id":"WHCS","slug":"whcs"},{"id":8,"school_type":"MS","name":"Next Wave Junior High","created_at":"2019-05-09T13:01:47.984Z","updated_at":"2019-05-09T13:01:47.984Z","local_id":"NW","slug":"nw"},{"id":9,"school_type":"HS","name":"Somerville High","created_at":"2019-05-09T13:01:47.990Z","updated_at":"2019-05-09T13:01:47.990Z","local_id":"SHS","slug":"shs"},{"id":10,"school_type":"HS","name":"Full Circle High School","created_at":"2019-05-09T13:01:47.997Z","updated_at":"2019-05-09T13:01:47.997Z","local_id":"FC","slug":"fc"},{"id":11,"school_type":"ECS","name":"Capuano Early Childhood Center","created_at":"2019-05-09T13:01:48.002Z","updated_at":"2019-05-09T13:01:48.002Z","local_id":"CAP","slug":"cap"},{"id":12,"school_type":"OTHER","name":"Parent Information Center","created_at":"2019-05-09T13:01:48.006Z","updated_at":"2019-05-09T13:01:48.006Z","local_id":"PIC","slug":"pic"},{"id":13,"school_type":"OTHER","name":"SPED Outplacement and Walk-in","created_at":"2019-05-09T13:01:48.011Z","updated_at":"2019-05-09T13:01:48.011Z","local_id":"SPED","slug":"sped"}],"grades":["3","4","5","6","7","8"]}; \ No newline at end of file diff --git a/app/controllers/reading_controller.rb b/app/controllers/reading_controller.rb index 4257de98d7..2a7c0a5891 100644 --- a/app/controllers/reading_controller.rb +++ b/app/controllers/reading_controller.rb @@ -60,28 +60,6 @@ def reading_json } end - # Used by ReadingDebugPage - def reading_debug_json - raise Exceptions::EducatorNotAuthorized unless current_educator.labels.include?('enable_reading_debug') - - students = authorized { Student.active.to_a } - students_json = students.as_json(only: [ - :id, - :first_name, - :last_name, - :grade, - :has_photo - ]) - reading_benchmark_data_points = ReadingBenchmarkDataPoint.all - .where(student_id: students.pluck(:id)) - .order(updated_at: :asc) - - render json: { - students: students_json, - reading_benchmark_data_points: reading_benchmark_data_points.as_json - } - end - # PUT # This allows fine-grained, cell-level edits to minimize conflicts, # Semantics are: idempotent, last write wins. Storage is append-only. @@ -118,6 +96,38 @@ def update_data_point_json render json: {}, status: 201 end + # Used by ReadingDebugPage + def reading_debug_json + raise Exceptions::EducatorNotAuthorized unless current_educator.labels.include?('enable_reading_debug') + + students = authorized { Student.active.to_a } + students_json = students.as_json(only: [ + :id, + :first_name, + :last_name, + :grade, + :has_photo + ]) + reading_benchmark_data_points = ReadingBenchmarkDataPoint.all + .where(student_id: students.pluck(:id)) + .order(updated_at: :asc) + + render json: { + students: students_json, + reading_benchmark_data_points: reading_benchmark_data_points.as_json + } + end + + # Used by ReadingDebugPage + def star_reading_debug_json + raise Exceptions::EducatorNotAuthorized unless current_educator.labels.include?('enable_reading_debug') + + grades = ['3','4','5','6','7','8'] + students = authorized { Student.active.where(grade: grades).to_a } + json = StarDebugQueries.new.fetch_json(students) + render json: json.merge(grades: grades) + end + private def ensure_authorized_for_feature! raise Exceptions::EducatorNotAuthorized unless current_educator.labels.include?('enable_reading_benchmark_data_entry') diff --git a/app/lib/star_debug_queries.rb b/app/lib/star_debug_queries.rb new file mode 100644 index 0000000000..15fc62c0b0 --- /dev/null +++ b/app/lib/star_debug_queries.rb @@ -0,0 +1,51 @@ +class StarDebugQueries + def fetch_json(students, options = {}) + cutoff_date = options.fetch(:cutoff_date, SchoolYear.first_day_of_school_for_year(2017)) + + star_readings = StarReadingResult.all + .where(student_id: students.pluck(:id)) + .where('date_taken > ?', cutoff_date) + students_json = students.as_json({ + only: [ + :id, + :first_name, + :last_name, + :grade, + :school_id + ] + }) + schools_json = School.all.as_json + + { + students: students_json, + star_readings: star_readings, + cutoff_date: cutoff_date, + schools: schools_json + } + end + + def fetch_csv(options = {}) + json = fetch_json(options) + students = json[:students] + star_readings = json[:star_readings] + + # to csv + students_by_id = {} + students.each {|s| students_by_id[s.id] = s } + rows = star_readings.map do |star| + student = students_by_id[star.student_id] + star.as_json.merge({ + school_id: student.school_id, + grade: student.grade + }) + end + hashes_to_csv_string(rows) + end + + private + def hashes_to_csv_string(row_hashes) + header_row = row_hashes.first.keys # all rows should have the same shape + body_rows = row_hashes.map {|row_hash| row_hash.values } + ([header_row] + body_rows).map {|row| row.to_csv }.join('') + end +end diff --git a/config/routes.rb b/config/routes.rb index 6bb47aa41b..4c71fb43f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -49,6 +49,7 @@ get '/api/reading/teams_json' => 'reading#teams_json' post '/api/reading/grouping_snapshot_json/:grouping_workspace_id' => 'reading#grouping_snapshot_json' get '/api/reading/reading_debug_json' => 'reading#reading_debug_json' + get '/api/reading/star_reading_debug_json' => 'reading#star_reading_debug_json' # classroom list creator get '/api/class_lists/workspaces_json' => 'class_lists#workspaces_json' @@ -199,6 +200,7 @@ resource :reading, only: [] do member do get '/debug' => 'ui#ui' + get '/debug_star' => 'ui#ui' end end diff --git a/ui/App.js b/ui/App.js index 3483042f8c..39d96f483c 100644 --- a/ui/App.js +++ b/ui/App.js @@ -32,6 +32,7 @@ import MyNotesPage from '../app/assets/javascripts/my_notes/MyNotesPage'; import ReadingEntryPage from '../app/assets/javascripts/reading/ReadingEntryPage'; import ReadingGroupingPage from '../app/assets/javascripts/reading/ReadingGroupingPage'; import ReadingDebugPage from '../app/assets/javascripts/reading_debug/ReadingDebugPage'; +import ReadingDebugStarPage from '../app/assets/javascripts/reading_debug/ReadingDebugStarPage'; import MyStudentsPage from '../app/assets/javascripts/my_students/MyStudentsPage'; import MySectionsPage from '../app/assets/javascripts/my_sections/MySectionsPage'; import StudentProfilePage from '../app/assets/javascripts/student_profile/StudentProfilePage'; @@ -101,6 +102,7 @@ export default class App extends React.Component { + @@ -170,6 +172,10 @@ export default class App extends React.Component { return ; } + renderReadingDebugStarPage(routeProps) { + return ; + } + renderReadingEntryPage(routeProps) { const {currentEducator} = this.props; const schoolSlug = routeProps.match.params.slug;