Skip to content

Commit f512ab3

Browse files
authored
feat: render external profiles (NASA-AMMOS#69)
- Add external profile subscribable - Create two resource stores for external and simulation - Create reusable sampleProfiles function
1 parent 0aaf3bc commit f512ab3

File tree

6 files changed

+144
-70
lines changed

6 files changed

+144
-70
lines changed

src/routes/plans/[id].svelte

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
resetPlanStores,
3131
viewTimeRange,
3232
} from '../../stores/plan';
33-
import { resetResourceStores } from '../../stores/resources';
33+
import { externalResources, resetResourceStores } from '../../stores/resources';
3434
import { resetSchedulingStores, schedulingStatus } from '../../stores/scheduling';
3535
import {
3636
modelParametersMap,
@@ -108,6 +108,7 @@
108108
$maxTimeRange = { end: $planEndTimeMs, start: $planStartTimeMs };
109109
$viewTimeRange = $maxTimeRange;
110110
111+
externalResources.setVariables({ planId: initialPlan.id });
111112
simulation.setVariables({ planId: initialPlan.id });
112113
simulationTemplates.setVariables({ modelId: initialPlan.model.id });
113114
}

src/stores/resources.ts

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,46 @@
1-
import { writable, type Writable } from 'svelte/store';
1+
import { derived, writable, type Writable } from 'svelte/store';
2+
import gql from '../utilities/gql';
3+
import { sampleProfiles } from '../utilities/resources';
4+
import { gqlSubscribable } from './subscribable';
5+
6+
/* Subscriptions. */
7+
8+
export const externalResources = gqlSubscribable<Resource[]>(
9+
gql.SUB_PROFILES_EXTERNAL,
10+
{ planId: -1 },
11+
[],
12+
(data: ProfilesExternalResponse) => {
13+
const { datasets, duration, start_time } = data;
14+
let resources: Resource[] = [];
15+
16+
for (const dataset of datasets) {
17+
const {
18+
dataset: { profiles },
19+
offset_from_plan_start,
20+
} = dataset;
21+
const sampledResources: Resource[] = sampleProfiles(profiles, start_time, duration, offset_from_plan_start);
22+
resources = [...resources, ...sampledResources];
23+
}
24+
25+
return resources;
26+
},
27+
);
228

329
/* Writeable. */
430

5-
export const resources: Writable<Resource[]> = writable([]);
31+
export const simulationResources: Writable<Resource[]> = writable([]);
32+
33+
/* Derived. */
34+
35+
export const resources = derived(
36+
[externalResources, simulationResources],
37+
([$externalResources, $simulationResources]) => {
38+
return [...$externalResources, ...$simulationResources];
39+
},
40+
);
641

742
/* Helper Functions. */
843

944
export function resetResourceStores() {
10-
resources.set([]);
45+
simulationResources.set([]);
1146
}

src/types/simulation.d.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
type ProfilesForPlanResponse = {
1+
type ProfilesExternalResponse = {
2+
datasets: [{ dataset: { profiles: Profile[] }; offset_from_plan_start: string }];
3+
duration: string;
4+
start_time: string;
5+
};
6+
7+
type ProfilesSimulationResponse = {
28
duration: string;
39
simulations: [{ datasets: [{ dataset: { profiles: Profile[] } }] }];
410
start_time: string;

src/utilities/effects.ts

+21-63
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
savingExpansionSet,
1313
} from '../stores/expansion';
1414
import { createModelError, createPlanError, creatingModel, creatingPlan, models, plan } from '../stores/plan';
15-
import { resources } from '../stores/resources';
15+
import { simulationResources } from '../stores/resources';
1616
import { schedulingStatus, selectedSpecId } from '../stores/scheduling';
1717
import { simulation, simulationStatus } from '../stores/simulation';
1818
import { view } from '../stores/views';
@@ -21,8 +21,9 @@ import { parseFloatOrNull, setQueryParam, sleep } from './generic';
2121
import gql from './gql';
2222
import { showConfirmModal } from './modal';
2323
import { reqGateway, reqHasura } from './requests';
24+
import { sampleProfiles } from './resources';
2425
import { Status } from './status';
25-
import { getDoyTime, getDoyTimeFromDuration, getDurationInMs, getIntervalFromDoyRange, getUnixEpochTime } from './time';
26+
import { getDoyTime, getDoyTimeFromDuration, getIntervalFromDoyRange } from './time';
2627
import { showFailureToast, showSuccessToast } from './toast';
2728

2829
/**
@@ -750,65 +751,6 @@ const effects = {
750751
}
751752
},
752753

753-
async getSampledResourcesForPlan(planId: number): Promise<Resource[]> {
754-
try {
755-
const data = await reqHasura<ProfilesForPlanResponse>(gql.GET_PROFILES_FOR_PLAN, { planId });
756-
const { plan } = data;
757-
const { duration, simulations, start_time } = plan;
758-
const [{ datasets }] = simulations;
759-
const [{ dataset }] = datasets;
760-
const { profiles } = dataset;
761-
762-
const planStart = getUnixEpochTime(getDoyTime(new Date(start_time)));
763-
const planDuration = getDurationInMs(duration);
764-
const sampledResources: Resource[] = [];
765-
766-
for (const profile of profiles) {
767-
const { name, profile_segments, type: profileType } = profile;
768-
const { type } = profileType;
769-
const values: ResourceValue[] = [];
770-
const schema = profileType?.schema ?? (profileType as ValueSchema);
771-
772-
for (let i = 0; i < profile_segments.length; ++i) {
773-
const segment = profile_segments[i];
774-
const nextSegment = profile_segments[i + 1];
775-
776-
const segmentOffset = getDurationInMs(segment.start_offset);
777-
const nextSegmentOffset = nextSegment ? getDurationInMs(nextSegment.start_offset) : planDuration;
778-
779-
const { dynamics } = segment;
780-
781-
if (type === 'discrete') {
782-
values.push({
783-
x: planStart + segmentOffset,
784-
y: dynamics,
785-
});
786-
values.push({
787-
x: planStart + nextSegmentOffset,
788-
y: dynamics,
789-
});
790-
} else if (type === 'real') {
791-
values.push({
792-
x: planStart + segmentOffset,
793-
y: dynamics.initial,
794-
});
795-
values.push({
796-
x: planStart + nextSegmentOffset,
797-
y: dynamics.initial + dynamics.rate * (nextSegmentOffset / 1000),
798-
});
799-
}
800-
}
801-
802-
sampledResources.push({ name, schema, values });
803-
}
804-
805-
return sampledResources;
806-
} catch (e) {
807-
console.log(e);
808-
return [];
809-
}
810-
},
811-
812754
async getSchedulingGoal(id: number | null | undefined): Promise<SchedulingGoal | null> {
813755
if (id !== null && id !== undefined) {
814756
try {
@@ -852,6 +794,22 @@ const effects = {
852794
}
853795
},
854796

797+
async getSimulationResources(planId: number): Promise<Resource[]> {
798+
try {
799+
const data = await reqHasura<ProfilesSimulationResponse>(gql.GET_PROFILES_SIMULATION, { planId });
800+
const { plan } = data;
801+
const { duration, simulations, start_time } = plan;
802+
const [{ datasets }] = simulations;
803+
const [{ dataset }] = datasets;
804+
const { profiles } = dataset;
805+
const resources: Resource[] = sampleProfiles(profiles, start_time, duration);
806+
return resources;
807+
} catch (e) {
808+
console.log(e);
809+
return [];
810+
}
811+
},
812+
855813
async getTsFilesActivityType(
856814
activityTypeName: string | null | undefined,
857815
modelId: number | null | undefined,
@@ -1114,8 +1072,8 @@ const effects = {
11141072
activitiesMap.set(keyBy(newActivities, 'id'));
11151073

11161074
// Resources.
1117-
const sampledResources: Resource[] = await effects.getSampledResourcesForPlan(planId);
1118-
resources.set(sampledResources);
1075+
const resources: Resource[] = await effects.getSimulationResources(planId);
1076+
simulationResources.set(resources);
11191077

11201078
checkConstraintsStatus.set(Status.Dirty);
11211079
simulationStatus.update(Status.Complete);

src/utilities/gql.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,8 @@ const gql = {
440440
}
441441
`,
442442

443-
GET_PROFILES_FOR_PLAN: `#graphql
444-
query GetProfilesForPlan($planId: Int!) {
443+
GET_PROFILES_SIMULATION: `#graphql
444+
query GetProfilesSimulation($planId: Int!) {
445445
plan: plan_by_pk(id: $planId) {
446446
duration
447447
simulations(limit: 1) {
@@ -734,6 +734,28 @@ const gql = {
734734
}
735735
`,
736736

737+
SUB_PROFILES_EXTERNAL: `#graphql
738+
subscription SubProfilesExternal($planId: Int!) {
739+
plan: plan_by_pk(id: $planId) {
740+
datasets {
741+
dataset {
742+
profiles {
743+
name
744+
profile_segments {
745+
dynamics
746+
start_offset
747+
}
748+
type
749+
}
750+
}
751+
offset_from_plan_start
752+
}
753+
duration
754+
start_time
755+
}
756+
}
757+
`,
758+
737759
SUB_SCHEDULING_GOALS: `#graphql
738760
subscription SubSchedulingGoals {
739761
goals: scheduling_goal {

src/utilities/resources.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { getDoyTime, getDurationInMs, getUnixEpochTime } from './time';
2+
3+
/**
4+
* Samples a list of profiles at their change points. Converts the sampled profiles to Resources.
5+
*/
6+
export function sampleProfiles(profiles: Profile[], startTime: string, duration: string, offset?: string): Resource[] {
7+
const planOffset = getDurationInMs(offset);
8+
const planStart = getUnixEpochTime(getDoyTime(new Date(startTime))) + planOffset;
9+
const planDuration = getDurationInMs(duration);
10+
const resources: Resource[] = [];
11+
12+
for (const profile of profiles) {
13+
const { name, profile_segments, type: profileType } = profile;
14+
const { type } = profileType;
15+
const values: ResourceValue[] = [];
16+
const schema = profileType?.schema ?? (profileType as ValueSchema);
17+
18+
for (let i = 0; i < profile_segments.length; ++i) {
19+
const segment = profile_segments[i];
20+
const nextSegment = profile_segments[i + 1];
21+
22+
const segmentOffset = getDurationInMs(segment.start_offset);
23+
const nextSegmentOffset = nextSegment ? getDurationInMs(nextSegment.start_offset) : planDuration;
24+
25+
const { dynamics } = segment;
26+
27+
if (type === 'discrete') {
28+
values.push({
29+
x: planStart + segmentOffset,
30+
y: dynamics,
31+
});
32+
values.push({
33+
x: planStart + nextSegmentOffset,
34+
y: dynamics,
35+
});
36+
} else if (type === 'real') {
37+
values.push({
38+
x: planStart + segmentOffset,
39+
y: dynamics.initial,
40+
});
41+
values.push({
42+
x: planStart + nextSegmentOffset,
43+
y: dynamics.initial + dynamics.rate * (nextSegmentOffset / 1000),
44+
});
45+
}
46+
}
47+
48+
resources.push({ name, schema, values });
49+
}
50+
51+
return resources;
52+
}

0 commit comments

Comments
 (0)