forecast.test.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. import { getNumberFormatter, NumberFormats } from '@superset-ui/core';
  20. import { SeriesOption } from 'echarts';
  21. import {
  22. extractForecastSeriesContext,
  23. extractForecastValuesFromTooltipParams,
  24. formatForecastTooltipSeries,
  25. rebaseForecastDatum,
  26. reorderForecastSeries,
  27. } from '../../src/utils/forecast';
  28. import { ForecastSeriesEnum } from '../../src/types';
  29. describe('extractForecastSeriesContext', () => {
  30. it('should extract the correct series name and type', () => {
  31. expect(extractForecastSeriesContext('abcd')).toEqual({
  32. name: 'abcd',
  33. type: ForecastSeriesEnum.Observation,
  34. });
  35. expect(extractForecastSeriesContext('qwerty__yhat')).toEqual({
  36. name: 'qwerty',
  37. type: ForecastSeriesEnum.ForecastTrend,
  38. });
  39. expect(extractForecastSeriesContext('X Y Z___yhat_upper')).toEqual({
  40. name: 'X Y Z_',
  41. type: ForecastSeriesEnum.ForecastUpper,
  42. });
  43. expect(extractForecastSeriesContext('1 2 3__yhat_lower')).toEqual({
  44. name: '1 2 3',
  45. type: ForecastSeriesEnum.ForecastLower,
  46. });
  47. });
  48. });
  49. describe('reorderForecastSeries', () => {
  50. it('should reorder the forecast series and preserve values', () => {
  51. const input: SeriesOption[] = [
  52. { id: `series${ForecastSeriesEnum.Observation}`, data: [10, 20, 30] },
  53. { id: `series${ForecastSeriesEnum.ForecastTrend}`, data: [15, 25, 35] },
  54. { id: `series${ForecastSeriesEnum.ForecastLower}`, data: [5, 15, 25] },
  55. { id: `series${ForecastSeriesEnum.ForecastUpper}`, data: [25, 35, 45] },
  56. ];
  57. const expectedOutput: SeriesOption[] = [
  58. { id: `series${ForecastSeriesEnum.ForecastLower}`, data: [5, 15, 25] },
  59. { id: `series${ForecastSeriesEnum.ForecastUpper}`, data: [25, 35, 45] },
  60. { id: `series${ForecastSeriesEnum.ForecastTrend}`, data: [15, 25, 35] },
  61. { id: `series${ForecastSeriesEnum.Observation}`, data: [10, 20, 30] },
  62. ];
  63. expect(reorderForecastSeries(input)).toEqual(expectedOutput);
  64. });
  65. it('should handle an empty array', () => {
  66. expect(reorderForecastSeries([])).toEqual([]);
  67. });
  68. it('should not reorder if no relevant series are present', () => {
  69. const input: SeriesOption[] = [{ id: 'some-other-series' }];
  70. expect(reorderForecastSeries(input)).toEqual(input);
  71. });
  72. it('should handle undefined ids', () => {
  73. const input: SeriesOption[] = [
  74. { id: `series${ForecastSeriesEnum.ForecastLower}` },
  75. { id: undefined },
  76. { id: `series${ForecastSeriesEnum.ForecastTrend}` },
  77. ];
  78. const expectedOutput: SeriesOption[] = [
  79. { id: `series${ForecastSeriesEnum.ForecastLower}` },
  80. { id: `series${ForecastSeriesEnum.ForecastTrend}` },
  81. { id: undefined },
  82. ];
  83. expect(reorderForecastSeries(input)).toEqual(expectedOutput);
  84. });
  85. });
  86. describe('rebaseForecastDatum', () => {
  87. it('should subtract lower confidence level from upper value', () => {
  88. expect(
  89. rebaseForecastDatum([
  90. {
  91. __timestamp: new Date('2001-01-01'),
  92. abc: 10,
  93. abc__yhat_lower: 1,
  94. abc__yhat_upper: 20,
  95. },
  96. {
  97. __timestamp: new Date('2001-01-01'),
  98. abc: 10,
  99. abc__yhat_lower: -10,
  100. abc__yhat_upper: 20,
  101. },
  102. {
  103. __timestamp: new Date('2002-01-01'),
  104. abc: 10,
  105. abc__yhat_lower: null,
  106. abc__yhat_upper: 20,
  107. },
  108. {
  109. __timestamp: new Date('2003-01-01'),
  110. abc: 10,
  111. abc__yhat_lower: 1,
  112. abc__yhat_upper: null,
  113. },
  114. ]),
  115. ).toEqual([
  116. {
  117. __timestamp: new Date('2001-01-01'),
  118. abc: 10,
  119. abc__yhat_lower: 1,
  120. abc__yhat_upper: 19,
  121. },
  122. {
  123. __timestamp: new Date('2001-01-01'),
  124. abc: 10,
  125. abc__yhat_lower: -10,
  126. abc__yhat_upper: 30,
  127. },
  128. {
  129. __timestamp: new Date('2002-01-01'),
  130. abc: 10,
  131. abc__yhat_lower: null,
  132. abc__yhat_upper: 20,
  133. },
  134. {
  135. __timestamp: new Date('2003-01-01'),
  136. abc: 10,
  137. abc__yhat_lower: 1,
  138. abc__yhat_upper: null,
  139. },
  140. ]);
  141. });
  142. it('should rename all series based on verboseMap but leave __timestamp alone', () => {
  143. expect(
  144. rebaseForecastDatum(
  145. [
  146. {
  147. __timestamp: new Date('2001-01-01'),
  148. abc: 10,
  149. abc__yhat_lower: 1,
  150. abc__yhat_upper: 20,
  151. },
  152. {
  153. __timestamp: new Date('2002-01-01'),
  154. abc: 10,
  155. abc__yhat_lower: null,
  156. abc__yhat_upper: 20,
  157. },
  158. {
  159. __timestamp: new Date('2003-01-01'),
  160. abc: 10,
  161. abc__yhat_lower: 1,
  162. abc__yhat_upper: null,
  163. },
  164. ],
  165. {
  166. abc: 'Abracadabra',
  167. __timestamp: 'Time',
  168. },
  169. ),
  170. ).toEqual([
  171. {
  172. __timestamp: new Date('2001-01-01'),
  173. Abracadabra: 10,
  174. Abracadabra__yhat_lower: 1,
  175. Abracadabra__yhat_upper: 19,
  176. },
  177. {
  178. __timestamp: new Date('2002-01-01'),
  179. Abracadabra: 10,
  180. Abracadabra__yhat_lower: null,
  181. Abracadabra__yhat_upper: 20,
  182. },
  183. {
  184. __timestamp: new Date('2003-01-01'),
  185. Abracadabra: 10,
  186. Abracadabra__yhat_lower: 1,
  187. Abracadabra__yhat_upper: null,
  188. },
  189. ]);
  190. });
  191. });
  192. test('extractForecastValuesFromTooltipParams should extract the proper data from tooltip params', () => {
  193. expect(
  194. extractForecastValuesFromTooltipParams([
  195. {
  196. marker: '<img>',
  197. seriesId: 'abc',
  198. value: [new Date(0), 10],
  199. },
  200. {
  201. marker: '<img>',
  202. seriesId: 'abc__yhat',
  203. value: [new Date(0), 1],
  204. },
  205. {
  206. marker: '<img>',
  207. seriesId: 'abc__yhat_lower',
  208. value: [new Date(0), 5],
  209. },
  210. {
  211. marker: '<img>',
  212. seriesId: 'abc__yhat_upper',
  213. value: [new Date(0), 6],
  214. },
  215. {
  216. marker: '<img>',
  217. seriesId: 'qwerty',
  218. value: [new Date(0), 2],
  219. },
  220. ]),
  221. ).toEqual({
  222. abc: {
  223. marker: '<img>',
  224. observation: 10,
  225. forecastTrend: 1,
  226. forecastLower: 5,
  227. forecastUpper: 6,
  228. },
  229. qwerty: {
  230. marker: '<img>',
  231. observation: 2,
  232. },
  233. });
  234. });
  235. test('extractForecastValuesFromTooltipParams should extract valid values', () => {
  236. expect(
  237. extractForecastValuesFromTooltipParams([
  238. {
  239. marker: '<img>',
  240. seriesId: 'foo',
  241. value: [0, 10],
  242. },
  243. {
  244. marker: '<img>',
  245. seriesId: 'bar',
  246. value: [100, 0],
  247. },
  248. ]),
  249. ).toEqual({
  250. foo: {
  251. marker: '<img>',
  252. observation: 10,
  253. },
  254. bar: {
  255. marker: '<img>',
  256. observation: 0,
  257. },
  258. });
  259. });
  260. const formatter = getNumberFormatter(NumberFormats.INTEGER);
  261. test('formatForecastTooltipSeries should apply format to value', () => {
  262. expect(
  263. formatForecastTooltipSeries({
  264. seriesName: 'abc',
  265. marker: '<img>',
  266. observation: 10.1,
  267. formatter,
  268. }),
  269. ).toEqual(['<img>abc', '10']);
  270. });
  271. test('formatForecastTooltipSeries should show falsy value', () => {
  272. expect(
  273. formatForecastTooltipSeries({
  274. seriesName: 'abc',
  275. marker: '<img>',
  276. observation: 0,
  277. formatter,
  278. }),
  279. ).toEqual(['<img>abc', '0']);
  280. });
  281. test('formatForecastTooltipSeries should format full forecast', () => {
  282. expect(
  283. formatForecastTooltipSeries({
  284. seriesName: 'qwerty',
  285. marker: '<img>',
  286. observation: 10.1,
  287. forecastTrend: 20.1,
  288. forecastLower: 5.1,
  289. forecastUpper: 7.1,
  290. formatter,
  291. }),
  292. ).toEqual(['<img>qwerty', '10, ŷ = 20 (5, 12)']);
  293. });
  294. test('formatForecastTooltipSeries should format forecast without observation', () => {
  295. expect(
  296. formatForecastTooltipSeries({
  297. seriesName: 'qwerty',
  298. marker: '<img>',
  299. forecastTrend: 20,
  300. forecastLower: 5,
  301. forecastUpper: 7,
  302. formatter,
  303. }),
  304. ).toEqual(['<img>qwerty', 'ŷ = 20 (5, 12)']);
  305. });
  306. test('formatForecastTooltipSeries should format forecast without point estimate', () => {
  307. expect(
  308. formatForecastTooltipSeries({
  309. seriesName: 'qwerty',
  310. marker: '<img>',
  311. observation: 10.1,
  312. forecastLower: 6,
  313. forecastUpper: 7,
  314. formatter,
  315. }),
  316. ).toEqual(['<img>qwerty', '10 (6, 13)']);
  317. });
  318. test('formatForecastTooltipSeries should format forecast with only confidence band', () => {
  319. expect(
  320. formatForecastTooltipSeries({
  321. seriesName: 'qwerty',
  322. marker: '<img>',
  323. forecastLower: 7,
  324. forecastUpper: 8,
  325. formatter,
  326. }),
  327. ).toEqual(['<img>qwerty', '(7, 15)']);
  328. });