Kamerplanter uses ArangoDB as its primary database — a multi-model system combining document and graph database capabilities. All entities are organized as document collections; relationships are modeled as edge collections in the named graph kamerplanter_graph.
The propagation_configs object-array field is the canonical model for a species' propagation. Each PropagationConfig entry describes one propagation method together with its own timing, wood stage, difficulty and notes — replacing the former flat propagation_methods/propagation_months/propagation_notes fields. Subfields:
months (list[int], range 1–12) — months in which this method is most suitable, deduplicated and sorted ascending on the server side, independent of the other configs. The species-level sowing windows for generative propagation remain in the separate direct_sow_months, indoor_start_months and transplant_months fields.
wood_stage (WoodStage \| null) — softwood / semi_hardwood / hardwood / herbaceous; only meaningful for cutting-type methods.
notes (string \| null, max. 1,000 characters) — free-text expert knowledge for this method: typical failure points, species-specific substrate or temperature requirements, acclimatisation steps.
The array is optional in SpeciesCreate (default: empty list) and always present in SpeciesResponse (empty list when not populated). It is maintained for most species in the seed data.
Note (deprecated flat fields): The flat fields propagation_methods/propagation_months/propagation_notes are deprecated in favour of propagation_configs. Legacy flat values are still accepted and adapted on import — one config per method, all sharing the flat propagation_months, with propagation_notes attached to the first method only. New data uses propagation_configs.
The toxicity object field captures a species' toxicity separately for cats, dogs and children (is_toxic_cats/is_toxic_dogs/is_toxic_children), the affected plant parts (toxic_parts), the responsible compounds (toxic_compounds) and the severity (severity: none/mild/moderate/severe). allergen_info adds contact- and pollen-allergen flags. The flat passthrough field toxicity_severity (low/moderate/high) is preserved from historical data and is not mapped onto toxicity.severity. All fields are optional (null when not maintained).
The seed_profile object field bundles genuine seed/germination metadata for seed-propagated species: germination temperature range (germination_temp_min_c/_max_c), sowing depth (sowing_depth_cm), days to germination (days_to_germination), seed viability in years (seed_viability_years), light/dark germination (light_germination: light/dark/indifferent), pretreatment (pretreatment: cold_stratification/warm_stratification/scarification/presoak), thousand-seed weight (thousand_seed_weight_g) and sowing density (sowing_density_per_m2). All fields are optional; purely vegetatively propagated species leave the object empty (null).
Kamerplanter automatically creates the following indexes on startup:
Collection
Index field
Type
Unique
species
scientific_name
Persistent
Yes
botanical_families
name
Persistent
Yes
slots
slot_id
Persistent
Yes
plant_instances
instance_id
Persistent
Yes
users
email
Persistent
Yes
tenants
slug
Persistent
Yes
memberships
user_key, tenant_key
Persistent
Yes
fertilizers
product_name, brand
Persistent
Yes
harvest_batches
batch_id
Persistent (sparse)
Yes
tanks
name
Persistent
Yes
refresh_tokens
token_hash
Persistent
Yes
calendar_feeds
token
Persistent
Yes
tasks
status, plant_key
Persistent
No
feeding_events
plant_key, timestamp
Persistent
No
Sparse indexes
An index marked sparse only considers documents in which the field is actually set. For harvest_batches.batch_id this is required because the batch ID is an optional field: empty input is normalised to null, and any number of batches may exist without a batch ID at the same time, while identifiers that are set must still stay unique. Existing data is migrated by v0030 (empty strings → null, index recreated as unique + sparse).
All tenant-bound resources carry a tenant_key field. The FastAPI dependency require_permission() automatically checks on each request whether the requesting user is a member of the corresponding tenant.
Global resources (no tenant binding): species, cultivars, botanical_families, pests, diseases, treatments, starter_kits