Home > Technical Reference > Flows > System Flows > Equipment Management: End-to-End Flow
Equipment Management: End-to-End Flow
Tags: equipment flow bow-setup statistics end-to-end architecture diagrams Status: ✅ ACTIVE | Complete documentation of equipment management system Last Updated: October 13, 2025
Related Documentation:
- Equipment-ViewModels - ViewModel architecture
- Equipment-Statistics - Performance tracking
- Equipment-Repositories - Repository layer (referenced)
- Round-Lifecycle-Flow - How equipment integrates with scoring
- MVVM-Patterns - Architecture patterns used
Overview
The Equipment Management system provides comprehensive tracking of archery equipment including bow setups, component management, equipment selection for rounds, and performance statistics aggregation. The system supports single-user equipment, guest equipment (multi-participant rounds), equipment versioning, and detailed statistics tracking per equipment configuration.
Key Capabilities:
- Create and manage complete bow setups with multiple components
- Track 10+ equipment types (riser, limbs, sight, stabilizer, arrows, etc.)
- Associate equipment with scoring rounds
- Track performance statistics per equipment configuration
- Version equipment setups to track changes over time
- Auto-generate guest setups for multi-participant rounds
- Advanced statistics including grouping analysis and fatigue detection
Architecture Layers
System Architecture Overview
graph TB subgraph "UI Layer" UI[Equipment Screens<br/>BowSetupDetailScreen<br/>EquipmentManagementScreen<br/>Component Detail Screens] end subgraph "ViewModel Layer" VM[ViewModels<br/>BowSetupViewModel<br/>Component ViewModels<br/>StateFlow Management] end subgraph "Repository Layer" REPO[BowSetupRepository<br/>Coordinates DAOs<br/>Business Logic] end subgraph "Database Layer" DAO[DAOs<br/>BowSetupDao<br/>EquipmentStatsDao<br/>Component DAOs] DB[(Room Database<br/>Equipment Tables<br/>Stats Cache)] end subgraph "Data Models" MODELS[Entities<br/>BowSetup<br/>Equipment Components<br/>Statistics Models] end UI -->|User Actions| VM VM -->|State Updates| UI VM -->|Data Operations| REPO REPO -->|CRUD Operations| DAO DAO -->|SQL Queries| DB DB -->|Results| DAO REPO -->|Entity Mapping| MODELS style UI fill:#e1f5ff style VM fill:#fff4e1 style REPO fill:#e8f5e9 style DAO fill:#f3e5f5 style DB fill:#ffebee style MODELS fill:#fce4ec
Layer Interaction Flow
sequenceDiagram participant User participant UI as UI Layer participant VM as ViewModel participant Repo as Repository participant DAO as DAO participant DB as Database User->>UI: Click "Save Setup" UI->>VM: saveBowSetup() VM->>VM: Validate Input VM->>Repo: insertBowSetup(bowSetup) Repo->>DAO: insertBowSetup() DAO->>DB: INSERT INTO bow_setup DB-->>DAO: Return ID DAO-->>Repo: Setup ID Repo->>DAO: insertBowSetupEquipment() DAO->>DB: INSERT INTO bow_setup_equipment DB-->>DAO: Success DAO-->>Repo: Success Repo-->>VM: SaveResult.Success VM->>VM: Update StateFlow VM-->>UI: State Change UI-->>User: Show Success Message
1. Data Models
Location: app/src/main/java/com/archeryapprentice/data/models/equipment/
Core Equipment Container
BowSetup (BowSetup.kt:18-52)
@Entity(tableName = "bow_setup")
data class BowSetup(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String = "",
val version: Int = 1, // Equipment version tracking
val notes: String = "",
val createdAt: Date = Date(),
val updatedAt: Date = Date(),
val isActive: Boolean = true, // Soft deletion
val isDefault: Boolean = false, // Default setup flag
// Guest setup support for multi-participant rounds
val isGuestSetup: Boolean = false, // Auto-generated for guests
val parentParticipantId: String? = null, // Links to SessionParticipant.id
val sourceRoundId: Int? = null // Round where guest setup was created
)Key Methods:
isSelectableByMainUser()- Filters out guest setups from user selectiongetDisplayName()- UI-friendly name with guest/default indicatorsisAutoGenerated()- Identifies system-generated setups
Equipment Components (17 Types)
Individual Components:
- Riser - Bow handle/grip (Riser.kt)
- Limbs - Bow limbs (Limbs.kt) with LimbLength enum
- Sight - Aiming sight (Sight.kt) with SightMarks
- Arrow - Arrow shaft (Arrow.kt) with ArrowPoint and ArrowNock
- BowString - Bow string (BowString.kt)
- Plunger - Button plunger (Plunger.kt)
- Rest - Arrow rest (Rest.kt)
- Stabilizer - Bow stabilizer (Stabilizer.kt) - Multi-item
- Weight - Balance weights (Weight.kt) - Multi-item
- Accessory - General accessories (Accessory.kt) - Multi-item
Equipment Junction Table:
@Entity(tableName = "bow_setup_equipment")
data class BowSetupEquipment(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val setupId: Long, // FK to BowSetup
val equipmentType: EquipmentType, // Type enum
val equipmentId: Long, // FK to specific equipment
val quantity: Int = 1, // For multi-item types
val addedAt: Date = Date()
)Composite View Model:
data class BowSetupWithEquipment(
val setup: BowSetup,
val riser: Riser? = null,
val limbs: Limbs? = null,
val sight: Sight? = null,
val stabilizers: List<Stabilizer> = emptyList(), // Multi-item
val plunger: Plunger? = null,
val rest: Rest? = null,
val bowString: BowString? = null,
val arrow: Arrow? = null,
val weights: List<Weight> = emptyList(), // Multi-item
val accessories: List<Accessory> = emptyList() // Multi-item
)Equipment Statistics Models
Location: app/src/main/java/com/archeryapprentice/data/models/EquipmentStatsModels.kt
Core Performance Metrics (EquipmentStatsModels.kt:39-54)
data class EquipmentPerformanceStats(
val bowSetupId: Long,
val totalArrows: Int,
val averageScore: Double,
val xCount: Int,
val tenCount: Int,
val nineOrBetterCount: Int,
val missCount: Int,
val firstShotAt: Long,
val lastShotAt: Long,
val roundCount: Int
) {
val accuracy: Double // (tenCount / totalArrows) * 100
val consistency: Double // (nineOrBetterCount / totalArrows) * 100
val missRate: Double // (missCount / totalArrows) * 100
}Advanced Analytics (EquipmentStatsModels.kt:94-150)
- AdvancedShotGrouping - Grouping analysis with eccentricity, bias detection
- FatigueMetrics - Performance decline analysis
- ConsistencyMetrics - End-to-end consistency tracking
- EquipmentDistancePerformance - Distance-specific stats
- ComparativeEquipmentStats - Cross-equipment comparison
Snapshot for Historical Tracking:
@Entity(tableName = "arrow_equipment_snapshot")
data class ArrowEquipmentSnapshot(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val arrowScoreId: Long, // FK to ArrowScore
val bowSetupId: Long, // Equipment used for this arrow
val bowSetupVersion: Int, // Version tracking
val riserId: Long? = null, // Component snapshots
val limbsId: Long? = null,
// ... all component IDs
val capturedAt: Long = System.currentTimeMillis()
)2. Database Layer
Location: app/src/main/java/com/archeryapprentice/data/dao/
BowSetupDao (BowSetupDao.kt:15-115)
CRUD Operations:
@Dao
interface BowSetupDao {
// Basic operations
@Insert suspend fun insertBowSetup(bowSetup: BowSetup): Long
@Update suspend fun updateBowSetup(bowSetup: BowSetup)
@Delete suspend fun deleteBowSetup(bowSetup: BowSetup)
@Query("UPDATE bow_setup SET isActive = 0 WHERE id = :id")
suspend fun softDeleteBowSetup(id: Long)
// Retrieval queries
@Query("SELECT * FROM bow_setup WHERE isActive = 1 ORDER BY updatedAt DESC")
fun getAllActiveBowSetups(): Flow<List<BowSetup>>
@Query("SELECT * FROM bow_setup WHERE id = :id AND isActive = 1")
suspend fun getBowSetupById(id: Long): BowSetup?
@Query("SELECT * FROM bow_setup WHERE isDefault = 1 AND isActive = 1 LIMIT 1")
suspend fun getDefaultBowSetup(): BowSetup?
// Guest setup queries (Lines 75-114)
@Query("SELECT * FROM bow_setup WHERE isGuestSetup = 1
AND parentParticipantId = :participantId
AND sourceRoundId = :roundId AND isActive = 1 LIMIT 1")
suspend fun getGuestSetupForParticipant(participantId: String, roundId: Int): BowSetup?
@Query("SELECT * FROM bow_setup WHERE isActive = 1 AND isGuestSetup = 0
ORDER BY isDefault DESC, updatedAt DESC")
fun getSelectableBowSetups(): Flow<List<BowSetup>>
}Equipment Junction Operations:
// BowSetupEquipment CRUD
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertBowSetupEquipment(bowSetupEquipment: BowSetupEquipment)
@Query("DELETE FROM bow_setup_equipment WHERE setupId = :setupId AND equipmentType = :equipmentType")
suspend fun deleteEquipmentByType(setupId: Long, equipmentType: EquipmentType)
@Query("SELECT * FROM bow_setup_equipment WHERE setupId = :setupId")
suspend fun getEquipmentForSetup(setupId: Long): List<BowSetupEquipment>EquipmentStatsDao (EquipmentStatsDao.kt:13-150)
Equipment Snapshot Operations:
@Dao
interface EquipmentStatsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertEquipmentSnapshot(snapshot: ArrowEquipmentSnapshot): Long
@Query("SELECT * FROM arrow_equipment_snapshot WHERE bowSetupId = :bowSetupId
AND bowSetupVersion = :version ORDER BY capturedAt DESC")
suspend fun getSnapshotsForBowSetupVersion(bowSetupId: Long, version: Int): List<ArrowEquipmentSnapshot>
}Equipment Usage Tracking:
@Query("SELECT DISTINCT bowSetupId, bowSetupVersion, COUNT(*) as usageCount
FROM arrow_equipment_snapshot
WHERE capturedAt >= :startDate
GROUP BY bowSetupId, bowSetupVersion
ORDER BY usageCount DESC")
suspend fun getEquipmentUsageStats(startDate: Long): List<EquipmentUsageSummary>Stats Cache Management:
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertStatsCache(cache: EquipmentStatsCache): Long
@Query("SELECT * FROM equipment_stats_cache
WHERE cacheKey = :cacheKey AND validUntil > :currentTime")
suspend fun getValidCacheEntry(cacheKey: String, currentTime: Long): EquipmentStatsCache?3. Repository Layer
Location: app/src/main/java/com/archeryapprentice/domain/repository/BowSetupRepository.kt
BowSetupRepository (BowSetupRepository.kt:41-377)
Initialization:
class BowSetupRepository(
private val bowSetupDao: BowSetupDao,
private val riserDao: RiserDao,
private val limbsDao: LimbsDao,
private val sightDao: SightDao,
// ... all component DAOs
)Core Operations:
// Setup retrieval
fun getAllBowSetups(): Flow<List<BowSetup>>
fun getSelectableBowSetups(): Flow<List<BowSetup>> // Excludes guest setups
suspend fun getBowSetupById(id: Long): BowSetup?
suspend fun getDefaultBowSetup(): BowSetup?
// Setup creation
suspend fun getOrCreateDefaultBowSetup(): BowSetup // Lines 65-80
suspend fun insertBowSetup(bowSetup: BowSetup): Long
suspend fun updateBowSetup(bowSetup: BowSetup)
suspend fun deleteBowSetup(bowSetup: BowSetup) // Soft deleteGuest Setup Management (Lines 95-158):
/**
* Get or create guest bow setup for participant in round
* Ensures each guest has their own setup for statistics tracking
*/
suspend fun getOrCreateGuestBowSetup(
participant: SessionParticipant,
roundId: Int
): BowSetup {
// Check if guest setup exists
val existingSetup = bowSetupDao.getGuestSetupForParticipant(participant.id, roundId)
if (existingSetup != null) return existingSetup
// Create new guest setup
val guestSetup = BowSetup(
name = "Guest Setup - ${participant.displayName}",
notes = "Auto-generated for guest participant",
isGuestSetup = true,
parentParticipantId = participant.id,
sourceRoundId = roundId,
isActive = true
)
val newId = insertBowSetup(guestSetup)
return getBowSetupById(newId) ?: guestSetup.copy(id = newId)
}
// Guest cleanup
suspend fun cleanupGuestSetupsForRound(roundId: Int)
suspend fun getGuestSetupStats(): GuestSetupStatsEquipment Management (Lines 219-257):
suspend fun addEquipmentToSetup(
setupId: Long,
equipmentType: EquipmentType,
equipmentId: Long,
quantity: Int = 1
) {
// Single-item types: Remove existing first
if (isSingleItemEquipment(equipmentType)) {
bowSetupDao.deleteEquipmentByType(setupId, equipmentType)
}
// Insert new equipment
val bowSetupEquipment = BowSetupEquipment(
setupId = setupId,
equipmentType = equipmentType,
equipmentId = equipmentId,
quantity = quantity
)
bowSetupDao.insertBowSetupEquipment(bowSetupEquipment)
// Update timestamp
getBowSetupById(setupId)?.let { setup ->
updateBowSetup(setup.copy(updatedAt = Date()))
}
}
suspend fun removeEquipmentFromSetup(setupId: Long, equipmentType: EquipmentType, equipmentId: Long)
suspend fun getEquipmentForSetup(setupId: Long): List<BowSetupEquipment>Complete Setup Assembly (Lines 260-312):
suspend fun getBowSetupWithEquipment(setupId: Long): BowSetupWithEquipment? {
val setup = getBowSetupById(setupId) ?: return null
val equipmentList = getEquipmentForSetup(setupId)
// Fetch all components by type
val riserEquipment = equipmentList.find { it.equipmentType == EquipmentType.RISER }
val riser = riserEquipment?.let { riserDao.getRiserById(it.equipmentId) }
val limbsEquipment = equipmentList.find { it.equipmentType == EquipmentType.LIMBS }
val limbs = limbsEquipment?.let { limbsDao.getLimbsById(it.equipmentId) }
// ... fetch all other components
// Multi-item equipment
val stabilizerEquipment = equipmentList.filter { it.equipmentType == EquipmentType.STABILIZER }
val stabilizers = stabilizerEquipment.mapNotNull {
stabilizerDao.getStabilizerById(it.equipmentId)
}
return BowSetupWithEquipment(
setup = setup,
riser = riser,
limbs = limbs,
sight = sight,
stabilizers = stabilizers,
// ... all components
)
}Versioning Support (Lines 315-348):
suspend fun createNewVersion(originalSetupId: Long, newName: String? = null): Long? {
val originalSetup = getBowSetupById(originalSetupId) ?: return null
val originalEquipment = getEquipmentForSetup(originalSetupId)
// Increment version
val nextVersion = originalSetup.version + 1
// Create new setup version
val newSetup = originalSetup.copy(
id = 0, // Auto-generate new ID
name = newName ?: originalSetup.name,
version = nextVersion,
createdAt = Date(),
updatedAt = Date()
)
val newSetupId = insertBowSetup(newSetup)
// Copy all equipment to new version
val newEquipmentList = originalEquipment.map {
it.copy(setupId = newSetupId, addedAt = Date())
}
bowSetupDao.insertBowSetupEquipmentList(newEquipmentList)
return newSetupId
}4. ViewModel Layer
Location: app/src/main/java/com/archeryapprentice/ui/equipment/viewModels/
Architecture Pattern
All equipment ViewModels follow consistent pattern:
class ComponentViewModel(private val repository: ComponentRepository) : ViewModel() {
// 1. Observable state for list
private val _items = MutableStateFlow<List<Component>>(emptyList())
val items: StateFlow<List<Component>> = _items.asStateFlow()
// 2. Input state for forms
private val _inputState = MutableStateFlow(ComponentInputState())
val inputState: StateFlow<ComponentInputState> = _inputState.asStateFlow()
// 3. CRUD operations
init { loadItems() }
suspend fun saveComponent(): SaveResult
fun deleteComponent(component: Component)
fun loadComponentForEdit(id: Long)
fun clearInputs()
// 4. Field update methods
fun updateBrand(value: String) {
_inputState.value = _inputState.value.copy(brand = value)
}
}See: Equipment-ViewModels for complete ViewModel documentation including:
- ArrowViewModel, StabilizerViewModel, SightViewModel, etc. (10 component ViewModels)
- BowSetupViewModel (coordinates complete setup)
- EquipmentListViewModel (inventory management)
Key Patterns:
- StateFlow: Reactive state management
- SaveResult: Consistent error handling
- InputState: Immutable form state
- Field Updates: Granular, type-safe updates
5. UI Layer
Location: app/src/main/java/com/archeryapprentice/ui/equipment/pages/
Main Equipment Screens
EquipmentManagementScreen.kt
- Entry point for equipment management
- Navigation hub to all equipment types
- Equipment type selection
SelectEquipmentTypeScreen.kt
- Grid/list view of equipment types
- Quick navigation to component entry
- Search and filter capabilities
BowSetupDetailScreen.kt (equipment/)
- Complete bow setup creation/editing
- Component selection for each type
- Equipment assembly and validation
- Performance statistics display (tabs)
Component Detail Screens (equipment/detailpages/)
Individual screens for each equipment type:
- ArrowDetailScreen.kt
- RiserDetailScreen.kt
- LimbsDetailScreen.kt
- SightDetailScreen.kt
- StabilizerDetailScreen.kt
- BowStringDetailScreen.kt
- PlungerDetailScreen.kt
- RestDetailScreen.kt
- WeightDetailScreen.kt
- AccessoryDetailScreen.kt
Pattern: All follow same layout:
- Input form with validation
- Equipment list display
- Edit/delete actions
- Save/cancel buttons
Equipment Analytics Screens (equipment/analytics/)
EquipmentAnalyticsHubScreen.kt
- Central analytics dashboard
- Performance overview
- Navigation to detailed views
EquipmentComparisonScreen.kt
- Side-by-side equipment comparison
- Performance metrics comparison
- Chart visualizations
EquipmentExportScreen.kt
- Export equipment data
- Statistics export
- Share functionality
Performance Statistics Tab
BowSetupPerformanceTab (component in BowSetupDetailScreen)
- Accuracy percentage by equipment
- Average score per arrow
- X count and 10 count
- Rounds completed with equipment
- Distance-specific performance
- Advanced grouping analysis
- Fatigue detection
- Consistency metrics
User Journeys
Journey 1: Create New Bow Setup
Flow: Equipment Hub → New Setup → Add Components → Save
flowchart TD Start([User clicks Equipment]) --> Hub[EquipmentManagementScreen] Hub --> NewSetup[Click New Bow Setup] NewSetup --> DetailScreen[BowSetupDetailScreen<br/>Create Mode] DetailScreen --> EnterName[Enter Setup Name<br/>updateName] EnterName --> SelectComponents{Select Components} SelectComponents -->|For each type| SelectRiser[Select Riser<br/>updateRiserId] SelectComponents -->|For each type| SelectLimbs[Select Limbs<br/>updateLimbsId] SelectComponents -->|For each type| SelectSight[Select Sight<br/>updateSightId] SelectComponents -->|For each type| SelectArrow[Select Arrow<br/>updateArrowId] SelectComponents -->|Multi-item| SelectStabs[Select Stabilizers<br/>updateStabilizerIds] SelectRiser --> ComponentsReady{All Components<br/>Selected?} SelectLimbs --> ComponentsReady SelectSight --> ComponentsReady SelectArrow --> ComponentsReady SelectStabs --> ComponentsReady ComponentsReady -->|Yes| ClickSave[Click Save Button] ComponentsReady -->|No| SelectComponents ClickSave --> VMSave[BowSetupViewModel<br/>saveBowSetup] VMSave --> Validate{Validate<br/>Input?} Validate -->|Invalid| ShowError[Show Validation<br/>Error] ShowError --> DetailScreen Validate -->|Valid| RepoInsert[BowSetupRepository<br/>insertBowSetup] RepoInsert --> DAOInsert[BowSetupDao<br/>insertBowSetup] DAOInsert --> DBInsert[(Database<br/>INSERT bow_setup)] DBInsert --> ReturnID[Return Setup ID] ReturnID --> AddEquipment[For Each Component<br/>addEquipmentToSetup] AddEquipment --> JunctionInsert[(Database<br/>INSERT bow_setup_equipment)] JunctionInsert --> UpdateTimestamp[Update Timestamp<br/>updatedAt = now] UpdateTimestamp --> Success[SaveResult.Success] Success --> ClearInputs[clearInputs] ClearInputs --> RefreshList[loadBowSetups] RefreshList --> Navigate[Navigate to List] Navigate --> End([Setup Created]) style Start fill:#e1f5ff style End fill:#c8e6c9 style ShowError fill:#ffcdd2 style Success fill:#c8e6c9
1. Navigate to Equipment (EquipmentManagementScreen)
User clicks "Equipment" in bottom nav
→ EquipmentManagementScreen displays
→ Shows equipment type options
2. Create New Setup (BowSetupDetailScreen)
User clicks "New Bow Setup"
→ BowSetupDetailScreen displays in create mode
→ BowSetupViewModel initializes empty InputState
- Location: BowSetupViewModel.kt:init
- Sets isEditing = false, editingId = 0
3. Enter Setup Details
User enters setup name: "Competition Setup"
→ BowSetupViewModel.updateName("Competition Setup")
- Location: BowSetupViewModel.kt:updateName()
- Updates inputState.name via StateFlow
4. Add Components (Per Component Type)
User clicks "Select Riser"
→ Navigation to RiserDetailScreen
→ User selects riser from list
→ BowSetupViewModel.updateRiserId(selectedRiser.id)
- Location: BowSetupViewModel.kt:updateRiserId()
Repeat for:
- Limbs (updateLimbsId)
- Sight (updateSightId)
- Arrows (updateArrowId)
- String (updateStringId)
- Plunger (updatePlungerId)
- Rest (updateRestId)
- Stabilizers (updateStabilizerIds) - Multi-item
- Weights (updateWeightIds) - Multi-item
5. Save Setup
User clicks "Save"
→ BowSetupViewModel.saveBowSetup()
- Location: BowSetupViewModel.kt:saveBowSetup()
- Validates inputState
- Creates BowSetup entity
- Calls BowSetupRepository.insertBowSetup()
Repository Flow:
→ BowSetupRepository.insertBowSetup(bowSetup)
- Location: BowSetupRepository.kt:82
- bowSetupDao.insertBowSetup(bowSetup) returns ID
→ For each component:
- BowSetupRepository.addEquipmentToSetup(setupId, type, componentId)
- Location: BowSetupRepository.kt:219-237
- Creates BowSetupEquipment junction entry
- bowSetupDao.insertBowSetupEquipment()
→ Updates setup timestamp
- getBowSetupById(setupId)
- updateBowSetup(setup.copy(updatedAt = Date()))
- Location: BowSetupRepository.kt:235-236
6. Success & Navigation
SaveResult.Success returned
→ BowSetupViewModel.clearInputs()
→ BowSetupViewModel.loadBowSetups() refreshes list
→ Navigate back to equipment list
→ New setup appears in list
File References:
- BowSetupDetailScreen.kt - UI composition
- BowSetupViewModel.kt - State management
- BowSetupRepository.kt:82, 219-237 - Data operations
- BowSetupDao.kt:19, 47 - Database operations
Journey 2: Select Equipment for Round
Flow: Round Creation → Equipment Selection → Associate with Round
flowchart TD Start([New Round]) --> RoundStart[RoundViewModel<br/>startNewRound] RoundStart --> EquipSelect[Equipment Selection<br/>Screen] EquipSelect --> QuerySetups[BowSetupRepository<br/>getSelectableBowSetups] QuerySetups --> DAOQuery[BowSetupDao<br/>Exclude Guest Setups] DAOQuery --> DBQuery[(Database<br/>WHERE isGuestSetup=0)] DBQuery --> DisplayList[Display Setup List<br/>Ordered by Default, Updated] DisplayList --> Search{User Searches?} Search -->|Yes| Filter[Client-Side Filter] Search -->|No| UserSelect Filter --> UserSelect[User Selects Setup] UserSelect --> UpdateVM[RoundViewModel<br/>updateSelectedBowSetup] UpdateVM --> CompleteRound[User Completes Round<br/>Setup] CompleteRound --> CreateRound[RoundViewModel<br/>createRound] CreateRound --> MultiCheck{Multi-Participant?} MultiCheck -->|No| InsertRound[RoundRepository<br/>insertRound<br/>with bowSetupId] MultiCheck -->|Yes| InsertRoundMP[RoundRepository<br/>insertRound] InsertRoundMP --> GuestLoop{For Each<br/>Guest Participant} GuestLoop --> CreateGuest[BowSetupRepository<br/>getOrCreateGuestBowSetup] CreateGuest --> CheckExist{Guest Setup<br/>Exists?} CheckExist -->|Yes| ReturnExisting[Return Existing<br/>Guest Setup] CheckExist -->|No| CreateNew[Create New<br/>Guest Setup] CreateNew --> InsertGuest[(Database<br/>INSERT guest setup)] InsertGuest --> LinkGuest[Link to participantId<br/>and roundId] LinkGuest --> ReturnExisting ReturnExisting --> MoreGuests{More Guests?} MoreGuests -->|Yes| GuestLoop MoreGuests -->|No| InsertRound InsertRound --> FirstArrow[User Shoots<br/>First Arrow] FirstArrow --> CreateSnapshot[Create<br/>ArrowEquipmentSnapshot] CreateSnapshot --> SnapshotData[Capture bowSetupId<br/>bowSetupVersion<br/>all component IDs] SnapshotData --> InsertSnapshot[(Database<br/>INSERT snapshot)] InsertSnapshot --> End([Round Ready<br/>Equipment Tracked]) style Start fill:#e1f5ff style End fill:#c8e6c9 style CreateGuest fill:#fff9c4 style CreateSnapshot fill:#f3e5f5
1. Start New Round (from RoundScoringScreen)
User clicks "New Round" in scoring
→ RoundViewModel.startNewRound()
- Location: RoundViewModel.kt:startNewRound()
- Initializes round creation state
2. Equipment Selection Prompt
During round creation flow:
→ Equipment selection screen displays
→ BowSetupRepository.getSelectableBowSetups()
- Location: BowSetupRepository.kt:59
- Returns Flow<List<BowSetup>> excluding guest setups
Database Query:
→ BowSetupDao.getSelectableBowSetups()
- Location: BowSetupDao.kt:85-91
- Query: WHERE isActive = 1 AND isGuestSetup = 0
- Orders by isDefault DESC, updatedAt DESC
3. Filter/Search
User enters search: "Competition"
→ ViewModel filters list client-side
→ Displays matching setups only
4. Select Setup
User selects "Competition Setup"
→ RoundViewModel.updateSelectedBowSetup(setupId)
- Location: RoundViewModel.kt:updateSelectedBowSetup()
- Stores setupId in round creation state
5. Create Round with Equipment
User completes round setup, clicks "Create Round"
→ RoundViewModel.createRound()
- Location: RoundViewModel.kt:createRound()
- Creates Round entity with bowSetupId
→ RoundRepository.insertRound(round)
- Saves round with FK to bow_setup.id
For Multi-Participant Rounds:
→ For each guest participant:
- BowSetupRepository.getOrCreateGuestBowSetup(participant, roundId)
- Location: BowSetupRepository.kt:99-127
- Auto-creates guest setup if not exists
- Associates with participant.id and roundId
6. Equipment Snapshot on First Arrow
When user shoots first arrow:
→ Arrow created with ArrowScore entity
→ ArrowEquipmentSnapshot created
- Location: During arrow save in RoundViewModel
- Captures bowSetupId, bowSetupVersion
- Captures all component IDs from setup
- timestamp = System.currentTimeMillis()
→ EquipmentStatsDao.insertEquipmentSnapshot(snapshot)
- Location: EquipmentStatsDao.kt:20
File References:
- RoundViewModel.kt - Round creation logic
- BowSetupRepository.kt:59, 99-127 - Equipment retrieval and guest setup
- BowSetupDao.kt:85-91 - Selectable setups query
- EquipmentStatsDao.kt:20 - Snapshot creation
Journey 3: View Equipment Statistics
Flow: Equipment List → Setup Detail → Performance Tab → Statistics Display
flowchart TD Start([Navigate to<br/>Equipment]) --> List[Equipment List<br/>getAllBowSetups] List --> Select[User Selects<br/>Competition Setup] Select --> LoadSetup[BowSetupViewModel<br/>loadBowSetupForEdit] LoadSetup --> RepoGet[BowSetupRepository<br/>getBowSetupWithEquipment] RepoGet --> FetchComponents[Fetch Setup +<br/>All Components] FetchComponents --> Display[BowSetupDetailScreen<br/>Displays] Display --> ClickPerf[User Clicks<br/>Performance Tab] ClickPerf --> PerfTab[BowSetupPerformanceTab<br/>Displays] PerfTab --> CheckCache{Check Stats<br/>Cache} CheckCache --> CacheQuery[EquipmentStatsDao<br/>getValidCacheEntry] CacheQuery --> CacheValid{Cache Valid?} CacheValid -->|Yes, validUntil > now| ReturnCached[Return Cached<br/>Statistics] ReturnCached --> DisplayStats CacheValid -->|No, miss or expired| QuerySnapshots[Query Arrow<br/>Snapshots] QuerySnapshots --> GetSnapshots[EquipmentStatsDao<br/>getSnapshotsForBowSetupVersion] GetSnapshots --> SnapshotsDB[(Database<br/>arrow_equipment_snapshot)] SnapshotsDB --> JoinArrows[Join to<br/>ArrowScore] JoinArrows --> Aggregate[Aggregate Metrics<br/>totalArrows, averageScore<br/>xCount, tenCount] Aggregate --> CalcDerived[Calculate Derived<br/>accuracy, consistency<br/>missRate] CalcDerived --> CreateStats[EquipmentPerformanceStats<br/>Data Class] CreateStats --> CacheResults[Create Cache Entry<br/>validUntil = now + duration] CacheResults --> InsertCache[(Database<br/>INSERT equipment_stats_cache)] InsertCache --> DisplayStats[Display Statistics] DisplayStats --> ShowBasic[Show Basic Stats<br/>Accuracy, Average, X Count<br/>10 Count, Consistency] ShowBasic --> AdvancedCheck{Advanced<br/>Analytics?} AdvancedCheck -->|No| DateFilter AdvancedCheck -->|Yes| CalcAdvanced[Calculate Advanced<br/>Shot Grouping<br/>Fatigue Metrics<br/>Consistency Analysis] CalcAdvanced --> ShowAdvanced[Display Advanced<br/>Eccentricity, Bias<br/>Performance Drop<br/>Trends] ShowAdvanced --> DateFilter DateFilter --> FilterDate{User Filters<br/>Date Range?} FilterDate -->|Yes| Invalidate[Invalidate Cache<br/>Recalculate with Filter] Invalidate --> QuerySnapshots FilterDate -->|No| End([Statistics<br/>Displayed]) style Start fill:#e1f5ff style End fill:#c8e6c9 style ReturnCached fill:#c8e6c9 style CalcAdvanced fill:#f3e5f5
1. Navigate to Equipment
User clicks "Equipment" in bottom nav
→ Equipment list displays all bow setups
→ BowSetupRepository.getAllBowSetups()
- Location: BowSetupRepository.kt:56
- Returns Flow<List<BowSetup>>
2. Select Setup
User clicks "Competition Setup"
→ Navigate to BowSetupDetailScreen(setupId)
→ BowSetupViewModel.loadBowSetupForEdit(setupId)
- Location: BowSetupViewModel.kt:loadBowSetupForEdit()
- Loads complete setup with equipment
→ BowSetupRepository.getBowSetupWithEquipment(setupId)
- Location: BowSetupRepository.kt:260-312
- Fetches setup + all components
- Returns BowSetupWithEquipment
3. View Performance Tab
User clicks "Performance" tab
→ BowSetupPerformanceTab displays
→ Triggers statistics calculation
4. Statistics Aggregation
// Check cache first
→ EquipmentStatsDao.getValidCacheEntry(cacheKey)
- Location: EquipmentStatsDao.kt:59
- Returns cached stats if valid
If cache miss or expired:
→ Calculate statistics from raw data
→ Query arrow snapshots:
- EquipmentStatsDao.getSnapshotsForBowSetupVersion(setupId, version)
- Location: EquipmentStatsDao.kt:38
- Returns List<ArrowEquipmentSnapshot>
→ Aggregate performance metrics:
- Total arrows, average score, X count, 10 count
- Calculate accuracy, consistency, miss rate
- Location: EquipmentStatsModels.kt:39-54
→ Cache results:
- Create EquipmentStatsCache entry
- Set validUntil = now + cacheDuration
- EquipmentStatsDao.insertStatsCache(cache)
5. Display Statistics
Performance Tab shows:
- **Accuracy**: (tenCount / totalArrows) * 100
- **Average Score**: Sum of all arrow scores / totalArrows
- **X Count**: Count of X-ring hits
- **10 Count**: Count of 10-ring hits
- **Consistency**: (nineOrBetterCount / totalArrows) * 100
- **Miss Rate**: (missCount / totalArrows) * 100
- **Rounds Completed**: Distinct round count
- **First Used**: Timestamp of first arrow
- **Last Used**: Timestamp of most recent arrow
6. Advanced Analytics (If Enabled)
Advanced Statistics Tab shows:
**Shot Grouping Analysis**:
→ AdvancedShotGrouping calculated
- Location: EquipmentStatsModels.kt:94-150
- Eccentricity (1.0 = circular, >1.0 = elliptical)
- Group center (x, y coordinates)
- Horizontal/vertical bias
- Primary axis angle
- Average group size
- Radial consistency
**Fatigue Metrics**:
→ FatigueMetrics calculated
- Location: EquipmentStatsModels.kt:155-175
- Recent vs. earlier performance comparison
- Performance drop percentage
- Grouping deterioration
- Fatigue score (0-1 scale)
- Recommendations based on fatigue level
**Consistency Analysis**:
→ ConsistencyMetrics calculated
- Location: EquipmentStatsModels.kt:180-202
- Score variation across ends
- Performance trend (improving/declining/stable)
- Consistency percentage
- Most/least consistent ends
**Distance-Specific Performance**:
→ EquipmentDistancePerformance queried
- Location: EquipmentStatsModels.kt:76-85
- Breakdown by distance (18m, 30m, 50m, 70m, etc.)
- Accuracy and score average per distance
- 10 count percentage per distance
7. Filter by Date Range
User selects date range: "Last 30 days"
→ Statistics recalculated with filter
→ Only arrows from selected range included
→ Cache invalidated, new cache entry created
File References:
- BowSetupDetailScreen.kt - Performance tab UI
- BowSetupViewModel.kt - Statistics state management
- BowSetupRepository.kt:260-312 - Setup + equipment retrieval
- EquipmentStatsDao.kt:38, 59 - Snapshot queries + cache
- EquipmentStatsModels.kt:39-54, 94-202 - Statistics calculation
Journey 4: Edit Equipment
Flow: Equipment List → Setup Detail → Edit Components → Save Changes
flowchart TD Start([Navigate to<br/>Equipment]) --> List[Equipment List] List --> SelectSetup[User Selects<br/>Competition Setup] SelectSetup --> LoadEdit[BowSetupViewModel<br/>loadBowSetupForEdit] LoadEdit --> SetEditMode[isEditing = true<br/>editingId = setupId] SetEditMode --> LoadData[BowSetupRepository<br/>getBowSetupWithEquipment] LoadData --> PopulateForm[Populate InputState<br/>with Current Data] PopulateForm --> DisplayForm[BowSetupDetailScreen<br/>Edit Mode] DisplayForm --> UserEdits[User Clicks<br/>Change Sight] UserEdits --> NavSight[Navigate to<br/>SightDetailScreen] NavSight --> SelectNewSight[User Selects<br/>New Sight] SelectNewSight --> UpdateVM[BowSetupViewModel<br/>updateSightId] UpdateVM --> BackToForm[Back to<br/>Detail Screen] BackToForm --> ClickSave[User Clicks<br/>Save Changes] ClickSave --> CheckActive{Active Rounds<br/>Using Equipment?} CheckActive -->|Yes| ShowWarning[Show Warning Dialog<br/>Equipment in Active Rounds] ShowWarning --> UserChoice{User Chooses} UserChoice -->|Create New Version| CreateVersion[BowSetupRepository<br/>createNewVersion] CreateVersion --> IncrementVer[Increment Version<br/>version = original + 1] IncrementVer --> CopySetup[Create New Setup<br/>with New Version] CopySetup --> CopyEquipment[Copy All Equipment<br/>to New Setup] CopyEquipment --> ApplyChanges[Apply Changes to<br/>New Version] ApplyChanges --> SaveSuccess UserChoice -->|Update Anyway| DirectUpdate UserChoice -->|Cancel| DisplayForm CheckActive -->|No| DirectUpdate[Validate Input] DirectUpdate --> ValidCheck{Valid?} ValidCheck -->|No| ShowError[Show Validation<br/>Error] ShowError --> DisplayForm ValidCheck -->|Yes| RepoUpdate[BowSetupRepository<br/>updateBowSetup] RepoUpdate --> UpdateTimestamp[Update Timestamp<br/>updatedAt = now] UpdateTimestamp --> RemoveOld[Remove Old Equipment<br/>removeEquipmentFromSetup] RemoveOld --> AddNew[Add New Equipment<br/>addEquipmentToSetup] AddNew --> InvalidateCache[Invalidate<br/>Statistics Cache] InvalidateCache --> SaveSuccess[SaveResult.Success] SaveSuccess --> Refresh[Refresh Setup List<br/>loadBowSetups] Refresh --> End([Setup Updated]) style Start fill:#e1f5ff style End fill:#c8e6c9 style ShowWarning fill:#fff9c4 style ShowError fill:#ffcdd2
1. Navigate to Equipment
User clicks "Equipment" in bottom nav
→ Equipment list displays
2. Select Existing Setup
User clicks "Competition Setup"
→ Navigate to BowSetupDetailScreen(setupId)
→ BowSetupViewModel.loadBowSetupForEdit(setupId)
- Location: BowSetupViewModel.kt:loadBowSetupForEdit()
- Sets isEditing = true, editingId = setupId
- Loads current setup data into inputState
→ BowSetupRepository.getBowSetupWithEquipment(setupId)
- Location: BowSetupRepository.kt:260-312
- Returns complete setup with all components
3. Modify Components
User clicks "Change Sight"
→ Navigation to SightDetailScreen
→ User selects new sight
→ BowSetupViewModel.updateSightId(newSightId)
- Location: BowSetupViewModel.kt:updateSightId()
- Updates inputState.sightId
4. Save Changes
User clicks "Save Changes"
→ BowSetupViewModel.saveBowSetup()
- Location: BowSetupViewModel.kt:saveBowSetup()
- Validates changes
- Since isEditing = true, performs update instead of insert
→ BowSetupRepository.updateBowSetup(bowSetup.copy(updatedAt = Date()))
- Location: BowSetupRepository.kt:84
- Updates setup timestamp
→ For changed components:
- BowSetupRepository.removeEquipmentFromSetup(setupId, type, oldId)
- Location: BowSetupRepository.kt:239-251
- Deletes old BowSetupEquipment entry
- BowSetupRepository.addEquipmentToSetup(setupId, type, newId)
- Location: BowSetupRepository.kt:219-237
- Inserts new BowSetupEquipment entry
5. Version Decision Point
User has option: "Create New Version" or "Update Existing"
If "Create New Version":
→ BowSetupViewModel.createNewVersionAndSave()
→ BowSetupRepository.createNewVersion(originalSetupId, newName)
- Location: BowSetupRepository.kt:315-348
- Increments version number
- Creates new BowSetup with version = original.version + 1
- Copies all equipment to new version
- Returns new setupId
→ Apply changes to new version setup
→ Original setup remains unchanged (historical record)
If "Update Existing":
→ Direct update as described in step 4
→ Existing setup modified
6. Handle Rounds Using Equipment
When equipment is modified:
→ Check if any active rounds use this equipment
- Query: rounds WHERE bowSetupId = setupId AND status != COMPLETED
If active rounds exist:
→ Show warning: "Equipment is used in active rounds"
→ Options:
1. "Create New Version" - Recommended (preserves history)
2. "Update Anyway" - Updates setup, active rounds see changes
3. "Cancel" - Abort edit
7. Statistics Cache Invalidation
On equipment update:
→ Invalidate statistics cache for this equipment
→ EquipmentStatsDao.deleteCacheForEquipment(setupId)
- Location: EquipmentStatsDao.kt:79
→ Next statistics view will recalculate from raw data
File References:
- BowSetupDetailScreen.kt - Edit UI
- BowSetupViewModel.kt - Edit state management
- BowSetupRepository.kt:84, 219-251, 315-348 - Update + versioning operations
- BowSetupDao.kt:22, 53, 56 - Database updates
- EquipmentStatsDao.kt:79 - Cache invalidation
Multi-Participant Equipment Flow
Guest Setup Auto-Creation
flowchart TD Start([Create Multi-Participant<br/>Round]) --> RoundCreate[RoundViewModel<br/>createMultiParticipantRound] RoundCreate --> Participants[Participants:<br/>Main User<br/>Guest: Alice<br/>Guest: Bob] Participants --> MainUser{Main User<br/>Equipment} MainUser --> SelectMain[User Selects<br/>Competition Setup] SelectMain --> AssignMain[Round.bowSetupId =<br/>selectedSetupId] Participants --> GuestLoop{For Each<br/>Guest Participant} GuestLoop --> GuestAlice[Guest: Alice] GuestAlice --> CheckAlice[BowSetupRepository<br/>getOrCreateGuestBowSetup<br/>participant=Alice, roundId] CheckAlice --> QueryAlice[BowSetupDao<br/>getGuestSetupForParticipant<br/>participantId=alice.id] QueryAlice --> ExistAlice{Guest Setup<br/>Exists?} ExistAlice -->|Yes| ReturnAlice[Return Existing<br/>Guest Setup - Alice] ExistAlice -->|No| CreateAlice[Create New BowSetup<br/>name=Guest Setup - Alice<br/>isGuestSetup=true] CreateAlice --> LinkAlice[parentParticipantId=alice.id<br/>sourceRoundId=roundId] LinkAlice --> InsertAlice[(Database<br/>INSERT bow_setup)] InsertAlice --> ReturnAlice ReturnAlice --> GuestBob[Guest: Bob] GuestBob --> CheckBob[getOrCreateGuestBowSetup<br/>participant=Bob] CheckBob --> CreateBob[Create or Return<br/>Guest Setup - Bob] CreateBob --> AssignMain AssignMain --> RoundReady[Round Created<br/>Equipment Assigned] RoundReady --> Shooting[During Shooting] Shooting --> AliceShot[Alice Shoots Arrow] AliceShot --> AliceSnap[ArrowEquipmentSnapshot<br/>bowSetupId=alice_guest_setup_id<br/>participantId=alice.id] Shooting --> BobShot[Bob Shoots Arrow] BobShot --> BobSnap[ArrowEquipmentSnapshot<br/>bowSetupId=bob_guest_setup_id<br/>participantId=bob.id] Shooting --> MainShot[Main User Shoots] MainShot --> MainSnap[ArrowEquipmentSnapshot<br/>bowSetupId=competition_setup_id<br/>participantId=main_user_id] AliceSnap --> Stats[Statistics Tracked<br/>Independently Per Participant] BobSnap --> Stats MainSnap --> Stats Stats --> SelectableCheck{Guest Setups<br/>Selectable?} SelectableCheck --> Isolation[NO - Guest Setups<br/>Excluded from Main User<br/>getSelectableBowSetups] Isolation --> Cleanup{Round<br/>Deleted?} Cleanup -->|Yes| CleanupGuest[cleanupGuestSetupsForRound<br/>Set isActive=false] Cleanup -->|No| End([Independent Equipment<br/>Tracking]) CleanupGuest --> End style Start fill:#e1f5ff style End fill:#c8e6c9 style CreateAlice fill:#fff9c4 style CreateBob fill:#fff9c4 style Stats fill:#f3e5f5
When creating multi-participant round:
1. Round Creation with Participants
User creates round with participants:
- Local User (Main User)
- Guest Archer 1 "Alice"
- Guest Archer 2 "Bob"
→ RoundViewModel.createMultiParticipantRound(participants)
2. Equipment Assignment
Main User:
→ User selects existing bow setup: "Competition Setup"
→ Round.bowSetupId = selectedSetupId
Guest Archers (Alice, Bob):
→ For each guest:
- BowSetupRepository.getOrCreateGuestBowSetup(participant, roundId)
- Location: BowSetupRepository.kt:99-127
Check if exists:
→ BowSetupDao.getGuestSetupForParticipant(participantId, roundId)
→ If exists: Return existing guest setup
If not exists:
→ Create new BowSetup:
- name = "Guest Setup - Alice"
- isGuestSetup = true
- parentParticipantId = alice.id
- sourceRoundId = roundId
- isActive = true
→ insertBowSetup(guestSetup)
→ Return new guest setup
3. Guest Setup Isolation
Guest setups are:
- NOT selectable by main user in dropdowns
- BowSetupDao.getSelectableBowSetups() excludes isGuestSetup = true
- BowSetup.isSelectableByMainUser() returns false for guest setups
- Only visible in guest's round context
- Associated with specific participantId + roundId
- Automatically cleaned up when round deleted
- BowSetupRepository.cleanupGuestSetupsForRound(roundId)
- Location: BowSetupRepository.kt:144-146
- Sets isActive = false for all guest setups in round
4. Statistics Tracking Per Participant
Each arrow shot in multi-participant round:
→ ArrowScore created with participantId
→ ArrowEquipmentSnapshot created with:
- bowSetupId = guest setup ID (for Alice/Bob)
- bowSetupId = user setup ID (for main user)
- participantId = shooter's ID
Statistics queries filter by participantId + bowSetupId:
→ Alice's performance with "Guest Setup - Alice"
→ Bob's performance with "Guest Setup - Bob"
→ Main User's performance with "Competition Setup"
→ All independent and separately tracked
5. Guest Setup Cleanup
Periodic maintenance (app startup):
→ BowSetupRepository.cleanupOrphanedGuestSetups()
- Location: BowSetupRepository.kt:195-216
- Finds guest setups where sourceRoundId doesn't exist
- Soft deletes orphaned setups (isActive = false)
- Returns count of cleaned setups
Statistics Tracking Deep Dive
Equipment Snapshot & Statistics Flow
flowchart TD Start([Arrow Shot]) --> CreateArrow[Create ArrowScore<br/>scoreValue, isX, etc.] CreateArrow --> ArrowSaved[(Database<br/>INSERT arrow_score)] ArrowSaved --> CreateSnap[Create ArrowEquipmentSnapshot] CreateSnap --> SnapData[Capture Data:<br/>bowSetupId<br/>bowSetupVersion<br/>riserId, limbsId, sightId<br/>all component IDs] SnapData --> SaveSnap[(Database<br/>INSERT arrow_equipment_snapshot<br/>capturedAt=now)] SaveSnap --> CheckCache{Statistics<br/>Cache Valid?} CheckCache -->|Yes| Continue[Continue Shooting] CheckCache -->|No| InvalidateCache[Invalidate Cache<br/>deleteCacheForEquipment] InvalidateCache --> Continue Continue --> ViewStats{User Views<br/>Statistics?} ViewStats -->|No| End([Snapshot Preserved]) ViewStats -->|Yes| QueryCache[EquipmentStatsDao<br/>getValidCacheEntry] QueryCache --> CacheCheck{Cache Valid?} CacheCheck -->|Yes, validUntil > now| ReturnCache[Return Cached<br/>EquipmentPerformanceStats] ReturnCache --> DisplayCache[Display Statistics] DisplayCache --> End CacheCheck -->|No, expired or missing| FetchSnapshots[EquipmentStatsDao<br/>getSnapshotsForBowSetupVersion] FetchSnapshots --> SnapshotsDB[(Database<br/>SELECT arrow_equipment_snapshot<br/>WHERE bowSetupId, version)] SnapshotsDB --> JoinArrows[Join to ArrowScore<br/>via arrowScoreId] JoinArrows --> ArrowsDB[(Database<br/>Get scoreValue, isX<br/>scoredAt for each arrow)] ArrowsDB --> AggregateBasic[Aggregate Basic Metrics:<br/>totalArrows = COUNT<br/>averageScore = AVG<br/>xCount = COUNT WHERE isX<br/>tenCount = COUNT WHERE value=10] AggregateBasic --> CalcDerived[Calculate Derived:<br/>accuracy = tenCount/total * 100<br/>consistency = 9+count/total * 100<br/>missRate = missCount/total * 100] CalcDerived --> CreateStats[Create EquipmentPerformanceStats<br/>Data Class] CreateStats --> CheckAdvanced{Calculate<br/>Advanced Analytics?} CheckAdvanced -->|Yes| CalcGrouping[Shot Grouping Analysis<br/>eccentricity, bias<br/>group center, axis angle] CalcGrouping --> CalcFatigue[Fatigue Detection<br/>recent vs earlier<br/>performance drop] CalcFatigue --> CalcConsistency[Consistency Analysis<br/>end-to-end variation<br/>performance trends] CalcConsistency --> CalcDistance[Distance-Specific<br/>Breakdown by distance<br/>accuracy per distance] CalcDistance --> AdvancedReady CheckAdvanced -->|No| AdvancedReady[Statistics Ready] AdvancedReady --> CacheIt[Create Cache Entry<br/>validUntil = now + duration] CacheIt --> InsertCache[(Database<br/>INSERT equipment_stats_cache<br/>statsData=JSON)] InsertCache --> DisplayStats[Display Statistics:<br/>Accuracy, Average Score<br/>X Count, 10 Count<br/>Consistency, Miss Rate<br/>+ Advanced if enabled] DisplayStats --> End style Start fill:#e1f5ff style End fill:#c8e6c9 style ReturnCache fill:#c8e6c9 style CalcGrouping fill:#f3e5f5 style CalcFatigue fill:#f3e5f5
Equipment Snapshot Capture
When: Every arrow shot is captured with equipment details
Process:
1. User shoots arrow
→ ArrowScore created (id, endScoreId, arrowNumber, scoreValue, ...)
→ ArrowScore.bowSetupId = current equipment ID
→ ArrowScore.bowSetupVersion = current version
2. Equipment snapshot created
→ ArrowEquipmentSnapshot entity:
- arrowScoreId: FK to ArrowScore
- bowSetupId: Equipment used
- bowSetupVersion: Version at time of shot
- riserId, limbsId, sightId, ...: All component IDs
- capturedAt: Timestamp
→ EquipmentStatsDao.insertEquipmentSnapshot(snapshot)
3. Cache invalidation
→ If new round or significant time gap:
- Invalidate relevant cache entries
- Force recalculation on next stats view
Why Snapshot?
- Historical accuracy: Equipment changes don’t affect past performance
- Version tracking: Compare performance across equipment versions
- Component analysis: Identify which component changes affect performance
Performance Metrics Calculation
Basic Statistics (EquipmentStatsModels.kt:39-54):
data class EquipmentPerformanceStats(
val bowSetupId: Long,
val totalArrows: Int, // COUNT(*) from snapshots
val averageScore: Double, // AVG(scoreValue) from arrow_scores
val xCount: Int, // COUNT WHERE isX = true
val tenCount: Int, // COUNT WHERE scoreValue = 10
val nineOrBetterCount: Int, // COUNT WHERE scoreValue >= 9
val missCount: Int, // COUNT WHERE scoreValue = 0
val firstShotAt: Long, // MIN(scoredAt)
val lastShotAt: Long, // MAX(scoredAt)
val roundCount: Int // COUNT(DISTINCT roundId)
) {
// Derived metrics
val accuracy: Double = (tenCount.toDouble() / totalArrows) * 100
val consistency: Double = (nineOrBetterCount.toDouble() / totalArrows) * 100
val missRate: Double = (missCount.toDouble() / totalArrows) * 100
}Query Path:
1. EquipmentStatsDao.getSnapshotsForBowSetupVersion(bowSetupId, version)
→ Returns List<ArrowEquipmentSnapshot>
2. For each snapshot, join to ArrowScore:
→ Get scoreValue, isX, scoredAt
3. Aggregate in memory or database:
→ Total arrows: COUNT(*)
→ Average score: SUM(scoreValue) / COUNT(*)
→ X count: COUNT WHERE isX = true
→ 10 count: COUNT WHERE scoreValue = 10
→ Miss count: COUNT WHERE scoreValue = 0
4. Calculate derived metrics:
→ accuracy = (10 count / total) * 100
→ consistency = (9+ count / total) * 100
5. Cache results:
→ EquipmentStatsCache entry with validUntil timestamp
Advanced Analytics
Shot Grouping Analysis (EquipmentStatsModels.kt:94-150):
Requires arrow coordinates (xCoordinate, yCoordinate, targetCenterX, targetCenterY)
Calculations:
1. Normalize coordinates relative to target center
2. Calculate group center (mean x, mean y)
3. Calculate average distance from group center
4. Calculate eccentricity (ellipse ratio):
- Fit ellipse to shot group
- eccentricity = major axis / minor axis
- 1.0 = circular, >1.0 = elliptical
5. Detect bias:
- horizontalBias = mean(x - groupCenterX)
- verticalBias = mean(y - groupCenterY)
6. Calculate primary axis angle (for elliptical groups)
Results:
→ AdvancedShotGrouping data class
→ Provides insights:
- "Circular grouping" vs "Highly directional"
- "Bias high and right" direction indicators
- "Excellent" / "Good" / "Fair" / "Needs improvement" quality
Fatigue Detection (EquipmentStatsModels.kt:155-175):
Compares recent performance to earlier performance:
1. Split arrows into:
- Recent shots: Last 20% of session
- Earlier shots: First 80% of session
2. Calculate metrics for each:
- Average score
- Average group size (if coordinates available)
3. Compare:
- performanceDrop = (earlierAvg - recentAvg) / earlierAvg
- groupingDeterioration = (recentGroupSize - earlierGroupSize) / earlierGroupSize
4. Compute fatigue score:
- fatigueScore = weighted average of drops
- 0-1 scale (0 = no fatigue, 1 = high fatigue)
5. Generate recommendations:
- < 0.3: "Maintain current intensity"
- 0.3-0.6: "Consider shorter sessions"
- > 0.6: "Reduce shot count or rest"
Consistency Analysis (EquipmentStatsModels.kt:180-202):
Analyzes end-to-end performance:
1. Group arrows by end number
2. Calculate score for each end
3. Compute variation:
- Standard deviation of end scores
- Coefficient of variation
4. Detect trends:
- Linear regression of end scores over time
- trend = slope of regression line
- Positive = improving, Negative = declining
5. Identify outliers:
- mostConsistentEnd = end with lowest variation
- leastConsistentEnd = end with highest variation
Distance-Specific Performance
EquipmentDistancePerformance (EquipmentStatsModels.kt:76-85)
Query:
→ JOIN arrow_equipment_snapshot
WITH arrow_scores (via arrowScoreId)
WITH end_scores (via endScoreId)
WITH rounds (via roundId)
→ GROUP BY bowSetupId, distance, distanceUnit
Aggregations:
- arrowCount: COUNT(*)
- averageScore: AVG(scoreValue)
- tenCount: COUNT WHERE scoreValue = 10
- xCount: COUNT WHERE isX = true
- nineOrBetterCount: COUNT WHERE scoreValue >= 9
Per Distance Results:
- 18 meters: 95% accuracy, avg 9.5
- 30 meters: 88% accuracy, avg 9.0
- 50 meters: 72% accuracy, avg 8.2
- 70 meters: 65% accuracy, avg 7.8
Insights:
→ Identifies optimal distance for equipment
→ Highlights distance-specific weaknesses
→ Guides practice focus
Cache Management
Cache Strategy:
@Entity(tableName = "equipment_stats_cache")
data class EquipmentStatsCache(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val cacheKey: String, // Unique identifier for query
val equipmentType: String, // "bow_setup", "riser", etc.
val equipmentId: Long, // FK to equipment
val statsData: String, // JSON-serialized stats
val createdAt: Long, // Cache creation time
val validUntil: Long, // Expiration timestamp
val lastRoundId: Long? = null // For invalidation
)Cache Lifecycle:
1. Statistics Request:
→ Check cache: EquipmentStatsDao.getValidCacheEntry(cacheKey, now)
→ If valid (validUntil > now): Return cached data
→ If invalid/missing: Proceed to calculation
2. Calculate Statistics:
→ Query raw data from database
→ Aggregate and calculate metrics
→ Serialize to JSON
3. Store in Cache:
→ Create EquipmentStatsCache entry
→ Set validUntil = now + cacheDuration (e.g., 1 hour)
→ insertStatsCache(cache)
4. Cache Invalidation:
Triggers:
- Equipment modified: deleteCacheForEquipment(setupId)
- New round completed: getCacheKeysForRound(roundId) + invalidate
- Manual refresh: deleteCacheByKey(cacheKey)
5. Periodic Cleanup:
→ App startup or background task
→ deleteExpiredCache(now)
→ Removes entries where validUntil <= now
Edge Cases & Error Handling
Equipment in Use - Cannot Delete
Scenario: User attempts to delete bow setup used in rounds
Handling:
1. Check usage:
→ Query: SELECT COUNT(*) FROM rounds WHERE bowSetupId = :setupId
2. If count > 0:
→ Show dialog: "Equipment used in X rounds. Cannot delete."
→ Options:
- "Archive" (soft delete: isActive = false)
- "Cancel"
3. If count = 0:
→ Proceed with deletion
→ bowSetupDao.softDeleteBowSetup(id)
File Reference: BowSetupRepository.kt:86 (soft delete method)
Missing Equipment in Historical Rounds
Scenario: Equipment deleted/archived, historical round references it
Handling:
1. Database integrity:
→ Foreign key constraint allows NULL for Round.bowSetupId
→ Historical data preserved
2. Display logic:
→ When loading round: getBowSetupById(roundId) may return null
→ UI displays "Equipment No Longer Available" or "Unknown Equipment"
3. Statistics:
→ ArrowEquipmentSnapshot preserves equipment IDs
→ Statistics still calculable from snapshots
→ Historical performance preserved even if equipment deleted
File Reference: BowSetupDao.kt:72 (includes inactive)
Validation Rules for Equipment Specs
Single-Item Equipment Types:
- RISER, LIMBS, SIGHT, PLUNGER, REST, BOW_STRING, ARROW
- Only one of each type allowed per setup
- Adding second item of same type replaces first
Implementation:
private fun isSingleItemEquipment(equipmentType: EquipmentType): Boolean {
return when (equipmentType) {
EquipmentType.RISER,
EquipmentType.LIMBS,
EquipmentType.SIGHT,
EquipmentType.PLUNGER,
EquipmentType.REST,
EquipmentType.BOW_STRING,
EquipmentType.ARROW -> true
EquipmentType.STABILIZER,
EquipmentType.WEIGHT,
EquipmentType.ACCESSORY -> false // Multi-item allowed
}
}
suspend fun addEquipmentToSetup(...) {
if (isSingleItemEquipment(equipmentType)) {
bowSetupDao.deleteEquipmentByType(setupId, equipmentType) // Remove existing
}
bowSetupDao.insertBowSetupEquipment(...) // Insert new
}File Reference: BowSetupRepository.kt:359-373
Multi-Item Equipment Types:
- STABILIZER, WEIGHT, ACCESSORY
- Multiple items of same type allowed
- Each has quantity field
Statistics with Incomplete Data
Missing Arrow Coordinates:
- Basic statistics (score, X count, etc.) always available
- Advanced grouping requires coordinates
- UI shows “Coordinate data not available for advanced grouping”
Insufficient Data:
- Minimum arrows for meaningful statistics: 10
- UI shows “More data needed (X/10 arrows)” if below threshold
- Fatigue analysis requires 20+ arrows
- Consistency requires 2+ ends
Calculation Safeguards:
// Prevent division by zero
val accuracy: Double = if (totalArrows > 0)
(tenCount.toDouble() / totalArrows) * 100
else 0.0
// Check minimum data requirements
fun canCalculateFatigue(): Boolean = totalArrows >= 20
fun canCalculateGrouping(): Boolean = arrowsWithCoordinates >= 10Testing Coverage
Unit Tests
Repository Tests:
BowSetupRepositoryTest- CRUD operations
- Guest setup creation/cleanup
- Equipment association
- Versioning logic
ViewModel Tests:
BowSetupViewModelTest- State management (StateFlow updates)
- Input validation
- Save/update/delete operations
- Component selection
DAO Tests:
BowSetupDaoTest- Database queries
- Guest setup filtering
- Equipment junction operations
Integration Tests
Equipment Flow Tests:
- Create setup → Add components → Save → Verify database
- Select equipment → Associate with round → Shoot arrows → Verify snapshots
- Load statistics → Verify aggregations → Check cache
UI Tests
Screen Tests:
BowSetupDetailScreenTest- UI composition
- Input validation
- Component selection navigation
EquipmentListScreenTest- List display
- Search/filter
- Navigation to detail
Future Enhancements
Equipment Recommendations
Based on Performance:
- Analyze statistics across equipment
- Identify best-performing configurations
- Suggest optimal equipment for specific distances
Implementation:
data class EquipmentRecommendation(
val bowSetupId: Long,
val confidenceScore: Double, // 0-1
val reason: String,
val performanceGain: Double // Expected improvement percentage
)
fun generateRecommendations(
userStats: List<EquipmentPerformanceStats>,
targetDistance: Distance
): List<EquipmentRecommendation>Equipment Marketplace/Database
Community Equipment Sharing:
- Public equipment database
- User-submitted configurations
- Performance ratings and reviews
- Equipment recommendations based on skill level
Photo Attachments
Visual Equipment Tracking:
- Add photo field to equipment entities
- Camera integration for quick capture
- Photo gallery view in equipment detail
- Before/after comparisons for tuning
Advanced Analytics
Machine Learning:
- Predict optimal equipment combinations
- Fatigue pattern recognition
- Shot prediction based on conditions
- Personalized coaching recommendations
Key Code References
Data Models
BowSetup.kt:18-52- Core equipment containerBowSetupWithEquipment.kt:4-16- Composite view modelEquipmentStatsModels.kt:39-54- Performance statsEquipmentStatsModels.kt:94-202- Advanced analytics
Database Layer
BowSetupDao.kt:15-115- Setup operationsBowSetupDao.kt:75-114- Guest setup queriesEquipmentStatsDao.kt:20-45- Snapshot operationsEquipmentStatsDao.kt:59-83- Cache management
Repository Layer
BowSetupRepository.kt:56-90- Core operationsBowSetupRepository.kt:99-158- Guest setup managementBowSetupRepository.kt:219-257- Equipment managementBowSetupRepository.kt:260-312- Complete setup assemblyBowSetupRepository.kt:315-348- Versioning
ViewModel Layer
- See Equipment-ViewModels for complete documentation
BowSetupViewModel.kt- Setup coordinationcomponentViewModels/*.kt- Individual component ViewModels
UI Layer
BowSetupDetailScreen.kt- Setup creation/editingEquipmentManagementScreen.kt- Entry pointequipment/analytics/*.kt- Analytics screens
Summary
The Equipment Management system is a comprehensive, multi-layered architecture for tracking archery equipment, associating equipment with scoring rounds, and analyzing performance statistics. The system supports:
✅ Complete Equipment Tracking - 10+ equipment types with versioning ✅ Seamless Round Integration - Equipment selection and association ✅ Multi-Participant Support - Auto-generated guest setups ✅ Historical Accuracy - Equipment snapshots preserve history ✅ Performance Analytics - Basic and advanced statistics ✅ Cache Optimization - Fast statistics retrieval ✅ Data Integrity - Soft deletion, validation, FK constraints
Architecture Layers: Data Models → Database → Repository → ViewModel → UI Key Patterns: StateFlow, SaveResult, InputState, Repository Pattern Testing: Unit, Integration, and UI tests
Visual Diagrams Added:
- Architecture overview with layer interactions
- Complete user journey flowcharts (4 journeys)
- Multi-participant guest setup flow
- Equipment snapshot and statistics aggregation flow
Status: ✅ ACTIVE | Production-ready equipment management system with comprehensive visual documentation Last Updated: October 13, 2025 Documentation Version: 1.1 - Added Mermaid diagrams