Code Changes to Implement
Intrusion Example
1. Frontend Component Layer
ButtonIntrusion.vue
- User clicks button →
handleClickVehicleIntrusion()
- Calls
this.loadIntrusion()
(mapped fromuseMetricsStore
)
2. Pinia Store Layer
MetricsStore.js
loadIntrusion()
method at line 719- Calls
this.api.loadIntrusion()
via WebSAT API plugin
3. API Client Layer
index.js
WebSatApiPlugin()
provides API access- API client makes HTTP request to backend
4. Django URL Routing
urls.py
- Routes to
include("core.urls")
urls.py
- Line 97:
path("intrusion", views.IntrusionAnalysis.as_view(), name="intrusion")
5. Django View Layer
views.py
IntrusionAnalysis
class at line 848- Uses
serializers.IntrusionInputSerializer
for input validation - Uses
serializers.VehicleIntrusionOutputSerializer
for output formatting - Calls
tasks.calculate_intrusion_task
6. Django Serializer Layer
serializers.py
- Input serialization with
IntrusionInputSerializer
- Output serialization with
VehicleIntrusionOutputSerializer
- Field validation using
IntrusionPointDataField
7. Celery Task Layer
tasks.py
calculate_intrusion_task()
at line 868- Calls
services.calculate_intrusion()
- Returns formatted result at line 883
8. Business Logic Layer
services.py
services.calculate_intrusion()
processes the actual intrusion calculations- Interfaces with the SAT (Signal Analysis Toolkit) library
9. WebSocket Response Layer
consumers.py
TaskConsumer
handles WebSocket communicationsend_chunked_json()
for large responsessend_websockets_task_result()
sends results back to frontend
10. Frontend Result Processing
MetricsStore.js
addMetric()
stores the result- Returns processed metric data to component
11. UI Display Layer
ButtonIntrusion.vue
gatherResultantIntrusionSummary()
processes resultsmakeIntrusionTab()
creates new tab
IntrusionContent.vue
- Displays intrusion data in plots and tables
- Uses plotting components for data visualization
Flow Summary:
Content → MetricsStore → API Client → URLs → Views → Serializers → Tasks → Services → SAT Library
The flow goes through Django’s URL routing, view classes, serializers for data validation, Celery tasks for async processing, and business logic services that interface with the SAT library for signal analysis calculations.
ButtonHoodPlot.vue → Backend API Call
To create a similar cascade for the ButtonHoodPlot.vue to make a backend call and display PedestrianHoodContent.vue, implement the following components following the same pattern:
1. sat.py: Complete the dispatch_pedestrian_hood
function
Make sure pedestrian_hood
is imported at the top of sat.py
// ...existing code...
def dispatch_pedestrian_hood(
hood_data: typing.List[dict],
vehicle_number: int,
) -> types.PedestrianHoodResult:
pedestrian_hood_input = pedestrian_hood.PedestrianHoodInput(
hood_data=hood_data,
vehicle_number=vehicle_number,
)
pedestrian_hood_output = pedestrian_hood.calculate_pedestrian_hood(pedestrian_hood_input)
pedestrian_hood_result = types.PedestrianHoodResult(
vehicle_number=pedestrian_hood_output.vehicle_number,
hood_plot_data=pedestrian_hood_output.hood_plot_data,
analysis_results=pedestrian_hood_output.analysis_results,
)
return pedestrian_hood_result
// ...existing code...
2. services.py: service function
Create the service function:
# ...existing code...
def calculate_pedestrian_hood(
hood_data: typing.List[dict],
vehicle_number: int,
) -> types.PedestrianHoodResult:
logger.debug(
"Calculating pedestrian hood for vehicle number %d",
vehicle_number,
)
# Call the SAT library function here
pedestrian_hood_result = sat.dispatch_pedestrian_hood(
hood_data=hood_data,
vehicle_number=vehicle_number
)
return pedestrian_hood_result
3. serializers.py: serializer classes
Create input and output serializers:
# ...existing code...
class PedestrianHoodInputSerializer(serializers.Serializer):
hood_data = serializers.ListField(child=serializers.DictField())
vehicle_number = serializers.IntegerField()
class PedestrianHoodOutputSerializer(serializers.Serializer):
vehicle_number = serializers.IntegerField()
hood_plot_data = serializers.DictField()
analysis_results = serializers.DictField()
4. tasks.py: task function
Create the Celery task:
# ...existing code...
@shared_task
@registry.register_function(
name="pedestrian_hood",
input_serializer=serializers.PedestrianHoodInputSerializer,
output_serializer=serializers.PedestrianHoodOutputSerializer,
)
def calculate_pedestrian_hood_task(validated_data):
logger.debug("Calculating pedestrian hood: %s", validated_data)
result = services.calculate_pedestrian_hood(
validated_data["hood_data"],
validated_data["vehicle_number"],
)
return {
"vehicle_number": result.vehicle_number,
"hood_plot_data": result.hood_plot_data,
"analysis_results": result.analysis_results,
}
5. views.py: view class
Create the view class for pedestrian hood analysis:
# ...existing code...
class PedestrianHoodAnalysis(SATView):
description = "Performs the pedestrian hood analysis."
input_serializer = serializers.PedestrianHoodInputSerializer
output_serializer = serializers.PedestrianHoodOutputSerializer
task = tasks.calculate_pedestrian_hood_task
@_sat_schema(description, input_serializer, output_serializer)
def post(self, request):
return super().post(request)
Add the endpoint to the configuration:
# ...existing code...
def get_endpoint_configuration(request):
context = {}
# ...existing endpoints...
context.update({
# ...existing endpoints...
"loadPedestrianHood": reverse("signal-methods:pedestrian_hood"),
# ...existing endpoints...
})
return JsonResponse(context)
6. urls.py: url routing
Add the URL pattern for pedestrian hood analysis:
# ...existing code...
sat_patterns = [
# ...existing patterns...
path("pedestrian_hood", views.PedestrianHoodAnalysis.as_view(), name="pedestrian_hood"),
# ...existing patterns...
]
7. api.js: API method
// ...existing code...
class WebSatApi {
constructor(
csrftoken,
{
// ...existing endpoints...
loadIntrusion,
loadPedestrianHood, // Add this line
loadLoadCellAnalysis,
// ...existing endpoints...
},
) {
this.csrftoken = csrftoken;
this.endpoints = {
// ...existing endpoints...
loadIntrusion,
loadPedestrianHood, // Add this line
loadLoadCellAnalysis,
// ...existing endpoints...
};
}
// ...existing methods...
// Add this method in the VEHICLE METRICS section
async loadPedestrianHood(hoodData, vehicleNumber) {
const data = {
hood_data: hoodData,
vehicle_number: vehicleNumber,
};
return await this.sendAsyncComputationRequest(
this.endpoints.loadPedestrianHood,
data,
);
}
// ...existing code...
}
8. MetricsStore.js: loading method
Add a loadPedestrianHood
method to the MetricsStore:
// ...existing code...
async loadPedestrianHood(hoodData, testNumber, vehicleNumber) {
const messageStore = useMessageStore();
const loadPedestrianHoodAndAddMetric = async (description) => {
try {
const pedestrianHoodResult = await messageStore.fetchAsync(
this.api.loadPedestrianHood(hoodData, vehicleNumber),
description,
);
const metric = this.addMetric(
"pedestrianHood",
"vehicle",
testNumber,
pedestrianHoodResult,
null,
null,
vehicleNumber,
);
return { description, result: metric };
} catch (error) {
throw new ErrorInputDescription(
error.name,
error.message,
description,
);
}
};
return loadPedestrianHoodAndAddMetric(`Pedestrian Hood Vehicle ${vehicleNumber}`);
},
9. ButtonHoodPlot.vue: button methods
Modify the button to make the backend call:
<script>
// ...existing imports...
import { useMetricsStore } from "@stores/MetricsStore";
import { useMessageStore } from "@stores/MessageStore";
export default {
// ...existing code...
methods: {
...mapActions(useTabsUIStore, ["addTab"]),
...mapActions(useMetricsStore, ["loadPedestrianHood"]),
async handleClickHoodPlot() {
const selectedTestId = this.activeTab.testNumber;
const vehicleInfo = useVehicleTestsStore()?.vehicleInfoLookup[selectedTestId]?.vehicle_information || [];
const vehicleNumbers = _.map(vehicleInfo, "vehicle_number");
// Filter vehicles that have hood data
const vehicleNumbersWithHoodData = vehicleNumbers.filter(
(vehicleNumber) => {
const vehicleData = useVehicleTestsStore()?.vehicleHoodData[selectedTestId]?.[vehicleNumber];
return vehicleData?.data && Array.isArray(vehicleData.data.hood_data) && vehicleData.data.hood_data.length > 0;
}
);
// Create promises for each vehicle
const hoodMetricsPromises = _.map(vehicleNumbersWithHoodData, (vehicleNumber) => {
const vehicleData = useVehicleTestsStore()?.vehicleHoodData[selectedTestId]?.[vehicleNumber];
const hoodDataProvenance = vehicleData.provenance;
return this.loadPedestrianHood(hoodDataProvenance, selectedTestId, vehicleNumber);
});
const hoodMetrics = await Promise.allSettled(hoodMetricsPromises)
.then((results) => useMessageStore().setCompletionMessage(results));
const hoodDataSummary = hoodMetrics
.filter((item) => item.status === "fulfilled")
.map((item) => {
const metricData = item.value?.result?.metricData;
const metricID = item.value?.result?.metricID;
if (metricData?.hood_plot_data) {
return { metricData, metricID };
}
return null;
})
.filter(Boolean);
this.makeHoodPlotTab(hoodDataSummary);
},
makeHoodPlotTab(hoodDataSummary) {
const tabContent = {
contentHoodPlotData: hoodDataSummary,
selectedHoodMetricId: null,
};
const tabName = `${this.activeTab.tabContentType[0]}${this.activeTab.testNumber}: Hood Plot`;
const tabTitle = `${_.capitalize(this.activeTab.tabContentType)} Test ${
this.activeTab.testNumber
}: Pedestrian Hood`;
this.addTab(
tabName,
tabTitle,
"pedestrianHood",
tabContent,
this.activeTab.testNumber,
);
const injButtonsUIStore = useInjButtonsUIStore();
injButtonsUIStore.handleTabAdded();
},
},
};
</script>
10. types.py: do I need these?
Create the result type:
# ...existing code...
@dataclass
class PedestrianHoodResult:
vehicle_number: int
hood_plot_data: dict
analysis_results: dict
11. TestsVehicleStore.js: handle hood data
// ...existing code...
export const useVehicleTestsStore = defineStore("testsVehicleStore", {
state: () => ({
// ...existing state...
vehicleHoodData: {}, // Add this line
// ...existing state...
}),
actions: {
// ...existing actions...
addVehicleHoodData(testNumber, vehicleNumber, vehicleHoodData) {
if (!this.vehicleHoodData[testNumber]) {
this.vehicleHoodData[testNumber] = {};
}
this.vehicleHoodData[testNumber][vehicleNumber] = vehicleHoodData;
},
// ...existing actions...
},
});
12. tabsUIStore: Update tab handling in the UI store?
Make sure TabsUIStore can handle the new “pedestrianHood” tab type, similar to how it handles “intrusion” tabs.
This implementation follows the exact same pattern as the intrusion analysis, creating a complete pipeline from the frontend button click through to the backend processing and display in the content component. The key differences are:
- Data structure: Adapted for hood plot data instead of intrusion data
- Component names: Changed to reflect pedestrian hood functionality
- Backend processing: Calls the specific SAT library function for hood analysis
The flow will be: ButtonHoodPlot.vue → MetricsStore.loadPedestrianHood()
→ Backend API → PedestrianHoodContent.vue display.