Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
1b9248d
hive: fix parts catalog sync re-fetching same page forever
spencerhhubert May 31, 2026
2dcb296
hive: persist parts catalog db across container recreates
spencerhhubert May 31, 2026
7dbd3eb
hive: versioned machine config backups (model, migration, endpoints)
mneuhaus Jun 4, 2026
62c8899
Merge catalog parts-sync fixes into sorthive
mneuhaus Jun 4, 2026
20b86a7
hive frontend: machine detail page with config backup history
mneuhaus Jun 4, 2026
3b1ae7c
hive frontend: expandable local_state JSON on machine detail page
mneuhaus Jun 4, 2026
69424c0
sorthive: add RK3588 camera transport and CM5 image bringup
mneuhaus Jun 4, 2026
6b68e38
sorthive: clean up CM5 bringup hygiene
mneuhaus Jun 4, 2026
8af2bbc
hive UI: codename-primary names, runtime format badge, inline benchma…
mneuhaus Jun 4, 2026
167711f
cameras: picker emits logical camera slots, not raw /dev indices
mneuhaus Jun 4, 2026
9ab81da
cameras: harden hardware H.264 WebRTC lifecycle across camera switches
mneuhaus Jun 4, 2026
0929ff2
cameras: restrict WebRTC ICE gathering to routable LAN interfaces
mneuhaus Jun 4, 2026
c767982
cameras: don't tear down a connected WebRTC peer; stabilize complianc…
mneuhaus Jun 5, 2026
a3c935b
cameras: render the hardware H.264 WebRTC track (stop aborting the of…
mneuhaus Jun 5, 2026
32b6f1f
ml: detection inference is NPU-only by default; CPU inference is opt-…
mneuhaus Jun 5, 2026
51c36e2
vision: C4 carousel detection uses the activated carousel-scope model…
mneuhaus Jun 5, 2026
8e54150
models: first-class experimental flag (Hive + sorter), hidden from de…
mneuhaus Jun 8, 2026
92f6ec4
training: multi-tier pull (--status repeatable, --max-rejected) + acc…
mneuhaus Jun 8, 2026
b4749cc
hive: experimental models don't consume a production codename
mneuhaus Jun 8, 2026
f978c53
hive: codenames advance one initial letter per release (Aqua -> Bronz…
mneuhaus Jun 8, 2026
c76f747
rknn: fp16 builder + recipe fix (i8 collapses fused-YOLO scores)
mneuhaus Jun 9, 2026
a2b6a06
hive ops: local SSH deploy + backup scripts; gitignore prod data dir
mneuhaus Jun 9, 2026
9ff28a4
hive deploy: force-recreate containers so new image actually runs
mneuhaus Jun 9, 2026
a753549
hive deploy: health-check /api/health (public), not /api/models (auth…
mneuhaus Jun 9, 2026
cc0b37c
hive web: show experimental models in catalog with an Experimental/St…
mneuhaus Jun 9, 2026
99ca4c0
hive deploy: verify containers picked up the new image, re-recreate i…
mneuhaus Jun 9, 2026
32483b5
sorter benchmark: RKNN single vs multi-core fan-out (drop per-core op…
mneuhaus Jun 9, 2026
3ab4246
sorter models UI: unify list, all-channel activate, per-model benchma…
mneuhaus Jun 9, 2026
f7ef4d5
sorter: remove camera calibration, rework capture pipeline (librga NV12)
mneuhaus Jun 9, 2026
d567c67
gitignore: cover local runtime junk (logs, sqlite backups, debug scre…
mneuhaus Jun 9, 2026
1981d3b
sorter/scripts: drop dead bricklink package, pick_menu, upload.py
mneuhaus Jun 9, 2026
2a58ad4
sorter frontend: remove dead settings sections and orphan components
mneuhaus Jun 9, 2026
3fc6fc3
hive frontend: remove dead ProfileRuleNode, TeacherPromptsEditor, unu…
mneuhaus Jun 9, 2026
cf9ddf1
training: drop dead Hailo path, v6 one-off scripts, orphan report scr…
mneuhaus Jun 9, 2026
6cc3aff
sorteros: remove dead sorteros-setup browser patcher, update install …
mneuhaus Jun 9, 2026
f027188
Merge remote-tracking branch 'origin/main' into sorthive
mneuhaus Jun 9, 2026
6c87167
sorteros build: official CM5-tablet vendor-6.1 base config + .7z base…
mneuhaus Jun 9, 2026
a4a1423
sorter: A/B benchmark harness for legacy vs RKMPP camera pipeline
mneuhaus Jun 9, 2026
8c1756c
sorteros build: cm5-tablet base sha256 + GPT root at p2 (verified aga…
mneuhaus Jun 9, 2026
4e60a04
sorteros: skip captive-portal onboarding on wired online uplink, bake…
mneuhaus Jun 9, 2026
34d7dda
sorter: WiFi setup section in settings (nmcli scan/connect/forget via…
mneuhaus Jun 9, 2026
73b8845
sorter frontend: dedicated Network page (wired status + WiFi setup) i…
mneuhaus Jun 9, 2026
ca9a052
sorter frontend: move Network (wired + WiFi) under Settings sidebar
mneuhaus Jun 9, 2026
73633e3
sorter network page: friendlier wired/wifi presentation (router+DNS, …
mneuhaus Jun 9, 2026
4e4ca8e
sorter wifi: scan runs automatically while the picker is visible (ini…
mneuhaus Jun 9, 2026
d913458
sorteros: drop experimental Armbian build path, keep maskrom flash to…
mneuhaus Jun 9, 2026
9753b54
media plane: fix leaked loop variable in invariant checks — unassigne…
mneuhaus Jun 9, 2026
01951d9
cameras: resolve logical index before enumerating v4l2 capture modes
mneuhaus Jun 9, 2026
40afa68
sorter frontend: move Tailscale section to settings/network page
mneuhaus Jun 9, 2026
227f6c8
hive link-machine: default to existing-machine selection when the acc…
mneuhaus Jun 9, 2026
64a7f14
sorter hive pairing: drop pointless target/machine-name fields from p…
mneuhaus Jun 9, 2026
cad272e
hive deploy: verify recreate via container start time, abort instead …
mneuhaus Jun 9, 2026
fc05a6d
benchmark: pause live perception inference while a model benchmark runs
mneuhaus Jun 9, 2026
ed05f9e
sorteros: pin CPU/NPU governors to performance (ondemand cost ~20% NP…
mneuhaus Jun 9, 2026
8d81742
gstreamer runtime: serialize gi import (thread race wedged capture th…
mneuhaus Jun 9, 2026
d4f8c0c
librga: accept MPP vstride-padded NV12 (1080p decoded as 1920x1088 br…
mneuhaus Jun 9, 2026
f881771
models: activation auto-selects the variant this machine executes (rk…
mneuhaus Jun 9, 2026
62dce44
cameras: one-click picture calibration (lock AE/AWB, converge exposur…
mneuhaus Jun 10, 2026
844e6b0
sorteros: persistent journald (vendor image ships Storage=volatile — …
mneuhaus Jun 10, 2026
029fb1b
capture: serialize UVC pipeline bring-up with 2s gap (simultaneous st…
mneuhaus Jun 10, 2026
0d741da
capture gate: bounded acquire so a wedged bring-up cannot starve othe…
mneuhaus Jun 10, 2026
121726e
sorteros: self-healing on kernel faults (hardware watchdog + panic au…
mneuhaus Jun 10, 2026
d746778
sorter: iStat-style system load widget in navbar (CPU sparkline, RAM,…
mneuhaus Jun 10, 2026
9b595d4
picture calibration: fresh-frame gating, exposure-cap detection, gamm…
mneuhaus Jun 10, 2026
a5fcfbe
picture calibration: highlight-percentile metering + bright-pixel WB …
mneuhaus Jun 10, 2026
ad9e7d2
picture calibration: bidirectional gain/gamma fallbacks (sensitive ca…
mneuhaus Jun 10, 2026
0cedfea
picture calibration: frame-averaged sampling + fix bracket detection …
mneuhaus Jun 10, 2026
a7e1a38
capture: re-apply persisted device settings after STREAMON (UVC firmw…
mneuhaus Jun 10, 2026
1d78b5a
calibration: verify+retry settings re-apply after STREAMON, persist c…
mneuhaus Jun 10, 2026
2173fa2
calibration: persist-only after success — post-calibration live appli…
mneuhaus Jun 10, 2026
a84cee4
camera controls: move per-control help texts into tooltips on the label
mneuhaus Jun 10, 2026
79631d7
tooltip: fixed positioning with viewport clamping (was clipped in scr…
mneuhaus Jun 10, 2026
8de2adc
camera views: instant poster snapshot while WebRTC connects
mneuhaus Jun 10, 2026
18b6234
camera poster: position via mediaLayout so zone overlays align from f…
mneuhaus Jun 10, 2026
8af7e3c
camera pipeline: cut backend CPU + instant WebRTC first frame
mneuhaus Jun 10, 2026
cc96fab
webrtc client: close peers on pagehide, shorten 503 retry backoff
mneuhaus Jun 10, 2026
6653542
cameras router: log blockers when a WebRTC offer is rejected with 503
mneuhaus Jun 10, 2026
0c427f1
feed metadata: never run overlay NPU detection on the event loop
mneuhaus Jun 10, 2026
e955e5f
feed metadata ws: first message skips detection overlays
mneuhaus Jun 10, 2026
59dee67
camera feed: scale region overlays by coordinate_space
mneuhaus Jun 10, 2026
725fda5
camera: stop rotating saved device settings onto the wrong camera
mneuhaus Jun 10, 2026
c04806c
connection guard: don't hide a working dashboard behind a busy event …
mneuhaus Jun 10, 2026
0f7d640
ui: show 'Connecting to sorter' instead of 'No machine selected'
mneuhaus Jun 10, 2026
0481391
waveshare: stabilize the servo bus stack
mneuhaus Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
software/sorter/backend/bundled_models/**/*.onnx filter=lfs diff=lfs merge=lfs -text
software/sorter/backend/bundled_models/**/*.bin filter=lfs diff=lfs merge=lfs -text
software/training/rknn_bundles/**/*.rknn filter=lfs diff=lfs merge=lfs -text
software/sorteros/build/overlay/usr/lib/librknnrt.so filter=lfs diff=lfs merge=lfs -text
software/sorteros/flash/rkbin/*.bin filter=lfs diff=lfs merge=lfs -text
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,17 @@ software/sorteros/v3/build/.env
/test.db
/test-profile-parts.db

# Local runtime state, logs, backups
/backups/
log/
logs/
local_state.sqlite*
.playwright-mcp/
/sorter-*.png
/software/sorter/frontend/sorter-*.png
/software/sorter/backend/aruco_config.json
/software/sorter/backend/data/
/software/sorteros/flash/*.img
/software/training/rknn_bundles/

.DS_Store
2 changes: 0 additions & 2 deletions docs/_data/nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ sections:
pages:
- title: Your first sort run
url: /sorter/tutorials/first-sort-run/
- title: Camera calibration
url: /sorter/camera-calibration/
- title: Troubleshooting
url: /sorter/troubleshooting/
- title: Under the hood
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Sorter V2 is an open-source LEGO sorting machine. Feed bulk LEGO into a hopper,
| Classification (Brickognize API) | Working | 98.5% accuracy on supported parts, 0.56s avg response |
| Object detection (chamber zone) | Working | NanoDet + YOLO11s trained, benchmarked on Pi 5 + Orange Pi 5 |
| Host software (Python backend) | WIP | Coordinator, state machines, vision manager functional |
| SvelteKit UI | WIP | Setup wizard, camera calibration, runtime dashboard in progress |
| SvelteKit UI | WIP | Setup wizard, camera controls, runtime dashboard in progress |
| Hive community platform | WIP | Upload pipeline, shared profiles, crowd verification |
| Electronics / PCB | WIP | Feeder + distribution board schematics in review (Rev 0.3) |
| Hardware CAD | WIP | V2 CAD mostly complete in Onshape, validation in progress |
Expand Down
13 changes: 3 additions & 10 deletions docs/lab/classification-research/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,29 +60,22 @@ Synthetic rendering is viable for augmentation. Varying angles, lighting, and po

## Color classification

ML is **unreliable** for color detection due to lighting variation across machines and environments. The recommended approach avoids ML entirely:

1. Place a color calibration card (reference colors) in frame.
2. Apply classic white-balance correction.
3. Use histogram matching against known reference patches.

This is more reliable than any ML-based color detection the team tested.
ML is **unreliable** for color detection due to lighting variation across machines and environments. The sorter currently treats color as a separate vision problem from part-shape classification, with camera exposure, white balance, and lighting kept under manual operator control.

## Trade-offs

- **Remote API vs local model** — Brickognize API works and is fast (0.56s), but requires internet. A local model is planned for offline operation. Strategy: dual-mode — local for common parts, API for rare/uncertain.
- **Embeddings vs classifier** — Embeddings need fewer examples per class but are fragile to bad vectors. Classifiers need more training data but are more robust. With enough data, classifiers win.
- **Breadth vs depth** — Supporting all 70k parts requires impractical amounts of data. Focusing on top 5k gives 85%+ coverage with achievable effort.
- **ML color vs calibration-based color** — ML color detection sounds elegant but fails across different lighting conditions. Calibration cards are boring but reliable.
- **ML color vs controlled capture** — ML color detection sounds elegant but fails across different lighting conditions. The practical path is to keep the capture environment controlled before adding any dedicated color classifier.

## What this is not

- **Not aiming for universal coverage.** Rare and obscure parts (post-2020 limited editions, regional exclusives) are out of scope for the initial classifier.
- **Not doing color via ML.** Color detection uses classical computer vision (calibration cards, white balance), not learned models.
- **Not doing color via ML.** Color handling is kept separate from the part classifier until the capture pipeline is stable enough for a dedicated color strategy.
- **Not a self-contained local system yet.** The primary classifier (Brickognize) is a remote API. Local self-hosting is planned but not shipped.

## Where to go next

- [Object detection research]({{ '/lab/object-detection/' | relative_url }}) — the detector that finds pieces in the chamber before classification
- [Sorter architecture]({{ '/sorter/architecture/' | relative_url }}) — how classification fits into the sorting pipeline
- [Camera calibration]({{ '/sorter/camera-calibration/' | relative_url }}) — the color calibration workflow that feeds into classification
70 changes: 0 additions & 70 deletions docs/sorter/camera-calibration.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/sorter/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ permalink: /sorter/
## Operate

- **[Your first sort run]({{ '/sorter/tutorials/first-sort-run/' | relative_url }})** — the end-to-end happy path: pick a profile, feed the machine, check a bin, stop cleanly.
- **[Camera calibration]({{ '/sorter/camera-calibration/' | relative_url }})** — focus with a Siemens Star, then auto-calibrate color with a SpyderCheckr 24.
- **[Sorter troubleshooting]({{ '/sorter/troubleshooting/' | relative_url }})** — symptom-led entries for install, first-run, and runtime problems.

## Under the hood
Expand Down
37 changes: 12 additions & 25 deletions docs/sorter/installation/sorter-os.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ kicker: Installation — SorterOS
lede: Flash SorterOS onto an SD card and configure it for first boot. The recommended way to run Sorter on an Orange Pi 5.
permalink: /sorter/installation/sorter-os/
audience: self-hosting operator
applies_to: sorteros v3.x
last_verified: 2026-05-19
applies_to: sorteros v4.x
last_verified: 2026-06-09
---

<div class="notice notice-warn">
Expand All @@ -32,40 +32,27 @@ SorterOS is a purpose-built OS image for Sorter, based on the official Ubuntu im

Go to **[github.com/basicallysource/sorter-v2/releases](https://github.com/basicallysource/sorter-v2/releases)**, find the latest SorterOS release, and download the `.zip` file from its assets.

## Step 2 — Configure WiFi and SSH

If you do not need to configure WiFi, hostname, SSH auth key, or Tailscale auth key, you can flash the `.zip` file directly — skip ahead to Step 3.

Otherwise, decompress the `.zip` file on your computer first, then open the `.img` file in **[SorterOS Setup](https://setup.basically.website)** to set those options before flashing. Everything runs client-side in the browser — nothing is sent to a server.

**Saving the configured image:**
- **Chrome** — SorterOS Setup can overwrite the original `.img` file directly, so no extra disk space is needed.
- **Other browsers** — a new copy is downloaded. The image is about 8 GB, so make sure you have the space. Once you have the configured copy you can delete the original.

The Pi needs an internet connection to complete first-boot initialization and to run Sorter on an ongoing basis. If you are not comfortable working in a terminal, configure WiFi here before flashing — the base image has no desktop environment, so WiFi cannot be set up through a GUI after booting.

Alternatively, plug the Pi into your router via Ethernet and it will come online automatically without any WiFi configuration.

If you ever need to change your WiFi network or SSH key, you can run the image through SorterOS Setup again — it will overwrite the previous configuration.

## Step 3 — Flash to SD card
## Step 2 — Flash to SD card

1. Open **[Balena Etcher](https://etcher.balena.io/)**
2. Click **Flash from file** and select either the `.zip` file (if you skipped setup) or the `.img` file (if you ran it through SorterOS Setup)
2. Click **Flash from file** and select the downloaded `.zip` file
3. Click **Select target** and choose your SD card
4. Click **Flash**

Wait for Etcher to finish writing and verifying. Do not remove the card until it reports success.
Wait for Etcher to finish writing and verifying. Do not remove the card until it reports success. The image ships generic — there is nothing to configure before flashing.

## Step 3 — Boot and connect to WiFi

## Step 4 — Boot
Insert the SD card into the Orange Pi 5 and power it on.

Insert the SD card into the Orange Pi 5 and power it on. SorterOS completes first-boot setup automatically, then starts the Sorter backend and UI. This takes less than 5 minutes if everything is working.
- **Ethernet** — if the Pi is wired to your router, it comes online automatically without any WiFi configuration.
- **WiFi** — when the Pi boots without a network connection, it opens a temporary hotspot named **`SorterOS-Setup-…`**. Join it from your phone or laptop; a captive portal opens automatically where you pick your WiFi network and enter its password. The Pi then shuts down the hotspot and joins your network.

Once first-boot initialization completes and the Pi has finished downloading its dependencies, the Sorter UI is available at:
The Pi needs an internet connection to complete first-boot initialization and to run Sorter on an ongoing basis. Once first-boot setup completes and the Pi has finished downloading its dependencies — less than 5 minutes if everything is working — the Sorter UI is available at:

**[http://sorter.local:5173/](http://sorter.local:5173/)**

This uses mDNS — the device you're browsing from must be on the same network as the Pi. If you set a custom hostname in SorterOS Setup, substitute that name for `sorter`.
This uses mDNS — the device you're browsing from must be on the same network as the Pi.

## SSH access

Expand Down
5 changes: 5 additions & 0 deletions software/hive/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ backend/sorthive.db

# Local runtime data
backend/data/
# Production bind-mount data (postgres datadir, parts.db, uploads). On the prod
# host this lives inside the repo working tree, so it MUST stay ignored — a
# `git stash -u` / `git clean -fdx` would otherwise wipe the live database.
# See scripts/deploy.sh, which also never runs those commands.
/data/

# Node / Svelte
node_modules/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""add machine_config_backups

Revision ID: b1c2d3e4f5a6
Revises: e9f0a1b2c3d4
Create Date: 2026-06-04

Versioned snapshots of a machine's settings (machine_params.toml + curated
local_state), pushed by the sorter. The sorter content-hashes before sending,
so a new row lands only when the config actually changed.
"""

from __future__ import annotations

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql


revision: str = "b1c2d3e4f5a6"
down_revision: Union[str, None] = "e9f0a1b2c3d4"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.create_table(
"machine_config_backups",
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("machine_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("content_hash", sa.String(), nullable=False),
sa.Column("payload", sa.JSON().with_variant(postgresql.JSONB(), "postgresql"), nullable=False),
sa.Column("trigger", sa.String(), nullable=False, server_default="config_change"),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["machine_id"], ["machines.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("machine_id", "version", name="uq_machine_config_backups_machine_version"),
)
op.create_index("ix_machine_config_backups_machine_id", "machine_config_backups", ["machine_id"])
op.create_index("ix_machine_config_backups_created_at", "machine_config_backups", ["created_at"])


def downgrade() -> None:
op.drop_index("ix_machine_config_backups_created_at", table_name="machine_config_backups")
op.drop_index("ix_machine_config_backups_machine_id", table_name="machine_config_backups")
op.drop_table("machine_config_backups")
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""add experimental flag to detection_models

Revision ID: c4d5e6f7a8b9
Revises: b1c2d3e4f5a6
Create Date: 2026-06-08

First-class flag so a model can be marked experimental. Experimental models are
hidden from the default Browse on both Hive and the sorters (opt back in with
?include_experimental=true) so operators don't install a test model by accident.
server_default false backfills existing rows.
"""

from __future__ import annotations

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


revision: str = "c4d5e6f7a8b9"
down_revision: Union[str, None] = "b1c2d3e4f5a6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.add_column(
"detection_models",
sa.Column(
"experimental",
sa.Boolean(),
nullable=False,
server_default=sa.false(),
),
)
op.create_index(
"ix_detection_models_experimental",
"detection_models",
["experimental"],
)


def downgrade() -> None:
op.drop_index("ix_detection_models_experimental", table_name="detection_models")
op.drop_column("detection_models", "experimental")
2 changes: 2 additions & 0 deletions software/hive/backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
api_keys,
auth,
leaderboard,
machine_config_backups,
machine_lookup,
machine_models,
machine_parts,
Expand Down Expand Up @@ -70,6 +71,7 @@ async def lifespan(_app: FastAPI):
app.include_router(admin.router)
app.include_router(admin_parts.router)
app.include_router(machines.router)
app.include_router(machine_config_backups.router)
app.include_router(machine_lookup.router)
app.include_router(profiles.router)
app.include_router(upload.router)
Expand Down
1 change: 1 addition & 0 deletions software/hive/backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Base(DeclarativeBase):
from app.models.sorting_profile_ai_message import SortingProfileAiMessage # noqa: E402, F401
from app.models.machine_profile_assignment import MachineProfileAssignment # noqa: E402, F401
from app.models.machine_set_progress import MachineSetProgress # noqa: E402, F401
from app.models.machine_config_backup import MachineConfigBackup # noqa: E402, F401
from app.models.detection_model import DetectionModel, DetectionModelVariant # noqa: E402, F401
from app.models.user_api_key import UserApiKey # noqa: E402, F401
from app.models.teacher_job import TeacherJob, TeacherJobItem # noqa: E402, F401
Expand Down
5 changes: 5 additions & 0 deletions software/hive/backend/app/models/detection_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class DetectionModel(Base):
scopes = Column(JSON_VARIANT, nullable=True)
training_metadata = Column(JSON_VARIANT, nullable=True)
is_public = Column(Boolean, nullable=False, default=True)
# Experimental models are first-class but hidden from the default Browse on
# both Hive and the sorters, so operators don't install a test model by
# accident. Opt back in with ?include_experimental=true.
experimental = Column(Boolean, nullable=False, default=False, server_default="false")
# Human-friendly handle drawn from a curated LEGO-color word list — like Ubuntu's
# codenames. Picked by :func:`app.services.codenames.next_codename` alphabetically
# at publish time; persistent so people can refer to "Bronze beats Aqua by 1.5 %"
Expand Down Expand Up @@ -62,6 +66,7 @@ class DetectionModel(Base):
Index("ix_detection_models_codename", "codename", unique=True),
Index("ix_detection_models_model_family", "model_family"),
Index("ix_detection_models_is_public", "is_public"),
Index("ix_detection_models_experimental", "experimental"),
)


Expand Down
Loading