@@ -42,16 +42,8 @@ function toGeoPair(
42
42
function toSourceSignalPair < S extends { id : string ; signal : string ; valueScaleFactor ?: number } > (
43
43
transfer : ( keyof EpiDataJSONRow ) [ ] ,
44
44
mixinValues : Partial < EpiDataRow > ,
45
- sensor : S | readonly S [ ] ,
45
+ sensor : readonly S [ ] ,
46
46
) {
47
- if ( ! isArray ( sensor ) ) {
48
- mixinValues . source = sensor . id ;
49
- mixinValues . signal = sensor . signal ;
50
- return {
51
- factor : sensor . valueScaleFactor ?? 1 ,
52
- sourceSignalPairs : SourceSignalPair . from ( sensor ) ,
53
- } ;
54
- }
55
47
const grouped = groupBySource ( sensor ) ;
56
48
57
49
let factor : number | ( ( row : EpiDataRow ) => number ) ;
@@ -97,8 +89,58 @@ function toSourceSignalPair<S extends { id: string; signal: string; valueScaleFa
97
89
} ;
98
90
}
99
91
92
+ function resolveBackwardOverrides (
93
+ rows : EpiDataRow [ ] ,
94
+ overrides : { level : RegionLevel ; fromId : string ; fromSignal : string ; toId : string ; toSignal : string } [ ] ,
95
+ ) : EpiDataRow [ ] {
96
+ if ( overrides . length === 0 ) {
97
+ return rows ;
98
+ }
99
+ function toKey ( id : string , signal : string , level : RegionLevel ) {
100
+ return `${ id } @${ signal } @${ level } ` ;
101
+ }
102
+ const over = new Map ( overrides . map ( ( o ) => [ toKey ( o . toId , o . toSignal , o . level ) , o ] ) ) ;
103
+ for ( const row of rows ) {
104
+ const key = toKey ( row . source , row . signal , row . geo_type ) ;
105
+ const signalOverride = over . get ( key ) ;
106
+ if ( signalOverride ) {
107
+ row . source = signalOverride . fromId ;
108
+ row . signal = signalOverride . fromSignal ;
109
+ }
110
+ }
111
+ return rows ;
112
+ }
113
+
114
+ function mapOverrides (
115
+ overrides : { level : RegionLevel ; fromId : string ; fromSignal : string ; toId : string ; toSignal : string } [ ] ,
116
+ typeSensors : readonly { id : string ; signal : string ; valueScaleFactor ?: number } [ ] ,
117
+ ) {
118
+ if ( overrides . length === 0 ) {
119
+ return typeSensors ;
120
+ }
121
+ return typeSensors . map ( ( d ) => {
122
+ for ( const o of overrides ) {
123
+ if ( o . fromId === d . id && o . fromSignal === d . signal ) {
124
+ return {
125
+ id : o . toId ,
126
+ signal : o . toSignal ,
127
+ valueScaleFactor : d . valueScaleFactor ,
128
+ } ;
129
+ }
130
+ }
131
+ return d ;
132
+ } ) ;
133
+ }
134
+
100
135
export default function fetchTriple <
101
- S extends { id : string ; signal : string ; format : Sensor [ 'format' ] ; isWeeklySignal : boolean } ,
136
+ S extends {
137
+ id : string ;
138
+ signal : string ;
139
+ format : Sensor [ 'format' ] ;
140
+ isWeeklySignal : boolean ;
141
+ overrides ?: Sensor [ 'overrides' ] ;
142
+ valueScaleFactor ?: number ;
143
+ } ,
102
144
> (
103
145
sensor : S | readonly S [ ] ,
104
146
region : Region | RegionLevel | readonly Region [ ] ,
@@ -120,6 +162,31 @@ export default function fetchTriple<
120
162
return asOf ;
121
163
}
122
164
165
+ function resolveForwardOverrides ( geoPairs : GeoPair | GeoPair [ ] , typeSensors : readonly S [ ] ) {
166
+ const levels = Array . from (
167
+ new Set < RegionLevel > ( Array . isArray ( geoPairs ) ? geoPairs . map ( ( d ) => d . level ) : [ geoPairs . level ] ) ,
168
+ ) ;
169
+ const overrides : { level : RegionLevel ; fromId : string ; fromSignal : string ; toId : string ; toSignal : string } [ ] = [ ] ;
170
+ for ( const sensor of typeSensors ) {
171
+ if ( ! sensor . overrides ) {
172
+ continue ;
173
+ }
174
+ for ( const level of levels ) {
175
+ if ( sensor . overrides [ level ] != null ) {
176
+ // override
177
+ overrides . push ( {
178
+ level,
179
+ fromId : sensor . id ,
180
+ fromSignal : sensor . signal ,
181
+ toId : sensor . overrides [ level ] ! . id ,
182
+ toSignal : sensor . overrides [ level ] ! . signal ,
183
+ } ) ;
184
+ }
185
+ }
186
+ }
187
+ return { overrides, levels } ;
188
+ }
189
+
123
190
function fetchImpl (
124
191
type : 'day' | 'week' ,
125
192
geoPairs : GeoPair | GeoPair [ ] ,
@@ -128,7 +195,6 @@ export default function fetchTriple<
128
195
typedMixinValues : Partial < EpiDataRow > ,
129
196
) {
130
197
typedMixinValues . time_type = type ;
131
- const { sourceSignalPairs, factor } = toSourceSignalPair ( typedTransfer , typedMixinValues , typeSensors ) ;
132
198
if ( date instanceof Date ) {
133
199
// single level and single date
134
200
typedMixinValues . time_value = type === 'day' ? toTimeValue ( date ) : toTimeWeekValue ( date ) ;
@@ -137,9 +203,52 @@ export default function fetchTriple<
137
203
} else {
138
204
typedTransfer . push ( 'time_value' ) ;
139
205
}
140
- return callAPI ( type , sourceSignalPairs , geoPairs , new TimePair ( type , date ) , typedTransfer , {
141
- asOf : fixAsOf ( ) ,
142
- } ) . then ( ( rows ) => parseData ( rows , typedMixinValues , factor ) ) ;
206
+ const timePair = new TimePair ( type , date ) ;
207
+
208
+ const { overrides, levels } = resolveForwardOverrides ( geoPairs , typeSensors ) ;
209
+
210
+ if ( overrides . length === 0 || levels . length === 1 ) {
211
+ // simple case: none or direct replacement
212
+ const mappedSensors = mapOverrides ( overrides , typeSensors ) ;
213
+ const { sourceSignalPairs, factor } = toSourceSignalPair ( typedTransfer , typedMixinValues , mappedSensors ) ;
214
+ return callAPI ( type , sourceSignalPairs , geoPairs , timePair , typedTransfer , {
215
+ asOf : fixAsOf ( ) ,
216
+ } ) . then ( ( rows ) => resolveBackwardOverrides ( parseData ( rows , typedMixinValues , factor ) , overrides ) ) ;
217
+ }
218
+
219
+ // multiple calls one for each mapped level
220
+ const mappedLevels = Array . from ( new Set ( overrides . map ( ( d ) => d . level ) ) ) ;
221
+ const calls : Promise < EpiDataRow [ ] > [ ] = [ ] ;
222
+ const geo = Array . isArray ( geoPairs ) ? geoPairs : [ geoPairs ] ;
223
+ for ( const mappedLevel of mappedLevels ) {
224
+ // compute subset of what needs to be mapped and can be transferred at once
225
+ const levelOverrides = overrides . filter ( ( d ) => d . level === mappedLevel ) ;
226
+ const levelGeo = geo . filter ( ( d ) => d . level === mappedLevel ) ;
227
+
228
+ const mappedSensors = mapOverrides ( levelOverrides , typeSensors ) ;
229
+ const levelTransfer = typedTransfer . slice ( ) ;
230
+ const levelMixins = { ...typedMixinValues } ;
231
+ const { sourceSignalPairs, factor } = toSourceSignalPair ( levelTransfer , levelMixins , mappedSensors ) ;
232
+ calls . push (
233
+ callAPI ( type , sourceSignalPairs , levelGeo , timePair , levelTransfer , {
234
+ asOf : fixAsOf ( ) ,
235
+ } ) . then ( ( rows ) => resolveBackwardOverrides ( parseData ( rows , levelMixins , factor ) , levelOverrides ) ) ,
236
+ ) ;
237
+ }
238
+ const unmappedLevels = levels . filter ( ( d ) => ! mappedLevels . includes ( d ) ) ;
239
+ if ( unmappedLevels . length > 0 ) {
240
+ // compute subset of what needs to be mapped and can be transferred at once
241
+ const levelGeo = geo . filter ( ( d ) => unmappedLevels . includes ( d . level ) ) ;
242
+ const levelTransfer = typedTransfer . slice ( ) ;
243
+ const levelMixins = { ...typedMixinValues } ;
244
+ const { sourceSignalPairs, factor } = toSourceSignalPair ( levelTransfer , levelMixins , typeSensors ) ;
245
+ calls . push (
246
+ callAPI ( type , sourceSignalPairs , levelGeo , timePair , levelTransfer , {
247
+ asOf : fixAsOf ( ) ,
248
+ } ) . then ( ( rows ) => parseData ( rows , levelMixins , factor ) ) ,
249
+ ) ;
250
+ }
251
+ return Promise . all ( calls ) . then ( ( r ) => ( [ ] as EpiDataRow [ ] ) . concat ( ...r ) ) ;
143
252
}
144
253
145
254
const [ day , week ] = splitDailyWeekly ( sensor ) ;
0 commit comments