bottom-board-management
This document describes the technical implementation of the hive bottom board feature for varroa mite monitoring. The feature spans three microservices: swarm-api, image-splitter, and web-app.
Architectureā
Service Dependenciesā
Data Flowā
-
User creates bottom box:
- web-app ā graphql-router ā swarm-api
- Creates record in
boxestable withtype = 'BOTTOM'
-
User uploads image:
- web-app ā graphql-router ā image-splitter
- Step 1:
uploadFrameSideuploads to S3, returnsfileId - Step 2:
addFileToBoxlinks file to box infiles_box_rel
-
Image processing:
- image-splitter queues varroa detection job
- Job processes image asynchronously
- Results stored in
detectedObjectsJSON field
Database Schemaā
swarm-api Schemaā
boxes tableā
CREATE TABLE `boxes` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int unsigned NOT NULL,
`hive_id` int NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`color` varchar(10) DEFAULT '#ffc848',
`position` mediumint DEFAULT NULL,
`type` enum('SUPER','DEEP','GATE','VENTILATION','QUEEN_EXCLUDER','HORIZONTAL_FEEDER','BOTTOM')
CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'DEEP',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
Migration: migrations/20251201025115_add_bottom_box_type.sql
image-splitter Schemaā
files_box_rel tableā
CREATE TABLE `files_box_rel` (
`box_id` int unsigned NOT NULL,
`file_id` int unsigned NOT NULL,
`user_id` int unsigned NOT NULL,
`inspection_id` INT NULL DEFAULT NULL,
`added_time` datetime DEFAULT CURRENT_TIMESTAMP,
INDEX (`user_id`, `box_id`, `inspection_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
Migration: migrations/018-box-files.sql
Purpose: Links uploaded files to specific boxes with inspection versioning support.
Relationshipsā
box_idāswarm-api.boxes.id(foreign service reference)file_idāimage-splitter.files.iduser_idā user ownershipinspection_idāswarm-api.inspections.id(NULL = current state)
GraphQL APIā
swarm-apiā
Add Box Mutationā
mutation addBox($hiveId: ID!, $position: Int!, $type: BoxType!) {
addBox(hiveId: $hiveId, position: $position, type: $type) {
id
type
position
color
}
}
Variables:
{
"hiveId": "123",
"position": 0,
"type": "BOTTOM"
}
Implementation: graph/schema.resolvers.go::AddBox()
image-splitterā
Upload File Mutationā
mutation uploadFrameSide($file: Upload!) {
uploadFrameSide(file: $file) {
id
url
resizes {
id
url
max_dimension_px
}
}
}
Returns:
{
"data": {
"uploadFrameSide": {
"id": "456",
"url": "https://s3.../original.jpg",
"resizes": [...]
}
}
}
Implementation: src/graphql/upload-frame-side.ts
Link File to Box Mutationā
mutation addFileToBox($boxId: ID!, $fileId: ID!, $hiveId: ID!) {
addFileToBox(boxId: $boxId, fileId: $fileId, hiveId: $hiveId)
}
Variables:
{
"boxId": "123",
"fileId": "456",
"hiveId": "789"
}
Implementation: src/graphql/resolvers.ts::addFileToBox()
Frontend Implementationā
BottomBox Componentā
Location: web-app/src/page/hiveEdit/bottomBox/BottomBox.tsx
Key Features:
- File upload with validation
- Two-step mutation process
- Error handling
- Loading states
Code Structure:
export default function BottomBox({ boxId, hiveId }) {
// Step 1: Upload file
const [uploadFile] = useUploadMutation(...)
// Step 2: Link to box
const [addFileToBoxMutation] = useMutation(...)
async function onFileSelect(event) {
const file = event.target.files?.[0]
// Upload file to S3
const uploadResult = await uploadFile({ file })
const fileId = uploadResult.data.uploadFrameSide.id
// Link file to box
await addFileToBoxMutation({ boxId, fileId, hiveId })
}
return (
<div>
<input type="file" onChange={onFileSelect} />
{data && <img src={data.uploadFrameSide.url} />}
</div>
)
}
Integration Pointsā
hiveButtons.tsx: Adds "Add bottom" button
<Button onClick={() => onBoxAdd(boxTypes.BOTTOM)}>
<span><T>Add bottom</T></span>
</Button>
index.tsx: Renders BottomBox component
{box && box.type === boxTypes.BOTTOM && (
<BottomBox boxId={boxId} hiveId={hiveId} />
)}
Backend Implementationā
swarm-api (Go)ā
Box Modelā
Location: graph/model/box.go
Key Methods:
Create()- Creates new box with type validationListByHive()- Fetches boxes for a hiveGet()- Retrieves single box
Type Constants:
const (
BoxTypeDeep BoxType = "DEEP"
BoxTypeSuper BoxType = "SUPER"
BoxTypeGate BoxType = "GATE"
BoxTypeVentilation BoxType = "VENTILATION"
BoxTypeQueenExcluder BoxType = "QUEEN_EXCLUDER"
BoxTypeHorizontalFeeder BoxType = "HORIZONTAL_FEEDER"
BoxTypeBottom BoxType = "BOTTOM"
)
image-splitter (TypeScript)ā
boxFile Modelā
Location: src/models/boxFile.ts
Key Methods:
export default {
async addBoxRelation(fileId, boxId, userId, inspectionId = null) {
await storage().query(
sql`INSERT INTO files_box_rel
(file_id, box_id, user_id, inspection_id)
VALUES (${fileId}, ${boxId}, ${userId}, ${inspectionId})`
);
},
async getBoxFiles(boxId, userId, inspectionId = null) {
// Returns files for specific box
}
}
Resolverā
Location: src/graphql/resolvers.ts
addFileToBox: async (_, {boxId, fileId, hiveId}, {uid}) => {
await boxFileModel.addBoxRelation(fileId, boxId, uid);
await fileModel.addHiveRelation(fileId, hiveId, uid);
return true;
}
Inspection Versioningā
How It Worksā
- Current State: Images have
inspection_id = NULL - Create Inspection:
- Current images are cloned
- Clones get new
inspection_id - Original images remain with
NULL(becomes new current state)
- Historical View: Query with specific
inspection_id
SQL Queriesā
Get current images:
SELECT * FROM files_box_rel
WHERE box_id = ? AND user_id = ? AND inspection_id IS NULL;
Get historical images:
SELECT * FROM files_box_rel
WHERE box_id = ? AND user_id = ? AND inspection_id = ?;
Job Queue Integrationā
Varroa Detection Jobā
When file is uploaded, a varroa detection job is queued:
await jobs.addJob(TYPE_VARROA, fileId);
Job Type: TYPE_VARROA = 'varroa'
Processing:
- Job worker picks up job from queue
- Downloads image from S3
- Runs varroa detection model
- Stores results in
detectedObjectsJSON field - Updates job status to complete
Deploymentā
Prerequisitesā
- MySQL database for both services
- AWS S3 bucket configured
- Redis for job queue
Migration Stepsā
- Deploy swarm-api:
cd swarm-api
just migrate-db-dev
# Applies: migrations/20251201025115_add_bottom_box_type.sql
- Deploy image-splitter:
cd image-splitter
just stop && just start
# Auto-runs: migrations/018-box-files.sql
- Deploy web-app:
cd web-app
pnpm build
# Upload dist/ to hosting
- Restart graphql-router:
docker restart gratheon-graphql-router-1
Verificationā
-- Check box type enum
SHOW COLUMNS FROM boxes LIKE 'type';
-- Check files_box_rel table
DESCRIBE files_box_rel;
-- Test data flow
INSERT INTO boxes (user_id, hive_id, type, position)
VALUES (1, 1, 'BOTTOM', 0);
-- Should return the box
SELECT * FROM boxes WHERE type = 'BOTTOM';
Testingā
Unit Testsā
swarm-api:
cd swarm-api
go test ./...
image-splitter:
cd image-splitter
npm test
web-app:
cd web-app
pnpm test:unit
Integration Test Flowā
- Create hive via API
- Add BOTTOM box to hive
- Upload image via BottomBox component
- Verify:
- File exists in S3
- Record in
filestable - Record in
files_box_reltable - Record in
files_hive_reltable - Varroa job queued
Manual Testā
- Navigate to hive in web-app
- Click "Add bottom" button
- Verify bottom box appears in structure
- Click on bottom box
- Upload image file
- Check database:
SELECT * FROM files_box_rel
ORDER BY added_time DESC LIMIT 1;
Troubleshootingā
Common Issuesā
Issue: "Data truncated for column 'type'"
- Cause: Migration not run
- Fix: Run
just migrate-db-devin swarm-api
Issue: "Unknown field addFileToBox"
- Cause: graphql-router not restarted
- Fix: Restart graphql-router to reload schema
Issue: "Unable to resolve table files_box_rel"
- Cause: Migration not run in image-splitter
- Fix: Rebuild image-splitter container
Issue: Image uploads but not linked
- Cause: Missing
addFileToBoxcall - Fix: Check BottomBox component calls both mutations
Future Enhancementsā
Phase 2: Display Imagesā
- Add query to fetch box images
- Display images in UI
- Show upload history
- Image deletion capability
Phase 3: Varroa Detectionā
- Implement varroa counting model
- Display count on images
- Show detection regions
- Confidence scores
Phase 4: Analyticsā
- Historical trend charts
- Treatment correlation
- Predictive alerts
- Comparison across hives
Related Documentationā
- [Bottom Board User Guide](../../about/products/web_app/starter-tier/š§® Hive bottom board & varroa monitoring.md)
- [DB Schemas](./š„ DB schemas/)
Change Logā
- 2025-12-01: Initial implementation
- Added BOTTOM box type
- Created files_box_rel table
- Implemented upload flow
- Added BottomBox component