Skip to content

Latest commit

Β 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SasiedzkAI

A senior monitoring system consisting of a .NET 10 + SQL Server backend (Docker) and a React Native mobile application (Android/iOS).


How the app works

Overview

SasiedzkAI is a support system for seniors where a senior shares location and shopping history with an assigned guardian. The backend automatically detects anomalies in the senior's daily activity and sends alerts to the guardian.

Senior (phone)                 Backend (.NET)              Guardian (phone/emulator)
      |                               |                               |
      |-- registration -------------->|                               |
      |<- userId (UUID) --------------|                               |
      |                               |       <-- registration -------|
      |                               |       --> userId (UUID) ------>|
      |                               |                               |
      | shares their UUID with guardian (outside the app)            |
      |                               |       <-- assign-guardian ----|
      |                               |           (with senior UUID)  |
      |                               |                               |
      |-- GPS every 5 min ----------->|                               |
      |-- save purchases (manual) --->|                               |
      |                               |-- hourly analysis ----------->|
      |                               |   (background service)        |-- alerts -->|
      |                               |                               |

Registration and roles

On first launch, the user chooses a role and data-sharing consents:

Option Senior Guardian
isSenior true false
sharesLocation can be enabled not used
sharesPurchases can be enabled not used

The backend assigns a persistent userId (UUID) based on the phone's deviceId. Reinstalling on the same device returns the same userId.


Senior side

Location tracking (useLocationTracking)

  • Hook runs every 5 minutes in the background (if consent = true)
  • Reads GPS (ACCESS_FINE_LOCATION, high accuracy, 15s timeout)
  • Sends POST /location/{userId} with coordinates and optional place category
  • Backend stores a LocationEvent only if SharesLocationData = true

Place categories

Locations can be tagged with category: cafe β˜•, store πŸ›’, pharmacy πŸ’Š, park 🌳, restaurant 🍽️, hospital πŸ₯

Purchase logging (RecordPurchaseScreen)

Senior manually adds a product with:

  • name and category (Dairy, Bread, Meat, Vegetables, Fruit, Drinks, Other)
  • amount (decimal)
  • optional estimated number of days until empty -> backend calculates ExpectedEmptyDate = PurchasedAt + N days

Shopping insights (ShoppingInsightsScreen)

GET /purchases/{userId}/insights returns products with ExpectedEmptyDate within 2 days - displayed with a warning ⚠️.

My ID (SeniorIdScreen)

Screen displays the senior's userId (UUID) to share with guardian - no API call.


Guardian side

Assigning to a senior (AssignGuardianScreen)

  1. Guardian displays their own userId on screen
  2. Enters senior's userId (UUID) shared by the senior
  3. POST /users/{seniorId}/assign-guardian - backend validates:
    • senior must exist and have IsSenior = true
    • guardian must exist and have IsSenior = false
  4. Sets senior.GuardianUserId = guardianId

My seniors list (MySeniorsScreen)

GET /users/{guardianId}/seniors - returns all seniors assigned to the guardian with sharesLocation and sharesPurchases flags.

Location history (LocationHistoryScreen)

GET /location/{userId}/history?lastDays=30 - returns 30 days of senior location history sorted by descending date.

Alerts (SeniorAlertsScreen)

GET /senioralerts/{guardianId} - unacknowledged alerts for this guardian's seniors:

Alert type Color Meaning
NoMovementToday warning πŸ”„ No GPS entries recorded today
NoOutdoorActivity warning πŸ”„ All GPS events are within a 200 m radius (senior did not leave home)
RoutineAnomaly warning πŸ”„ Deviation from typical activity
LongImmobility main πŸ›‘ Senior has not moved for a long time
EmergencyDetected dangerous 🚨 Critical situation

Background Service - automatic alerts

ActivityAlertBackgroundService runs on the backend every hour between 10:00 and 18:00 (local time).

Algorithm for each senior with location sharing enabled:

  1. Fetch today's GPS events
  2. If empty -> NoMovementToday alert
  3. If all points are within 200 m of the first point (Haversine formula) -> NoOutdoorActivity alert
  4. Duplicate protection: checks ExistsForSeniorTodayAsync() - one alert per type per day

Backend architecture (CQRS)

HTTP Controller
    |
    |-- Command (write)         --> Repository --> SQL Server
    |     RegisterUserCommand
    |     AssignGuardianCommand
    |     RecordLocationCommand
    |     RecordPurchaseCommand
    |
    \-- Query (read)            --> Repository --> SQL Server
          GetUserProfileQuery
          GetGuardianSeniorsQuery
          GetLocationHistoryQuery
          GetCrowdHeatmapQuery
          GetShoppingInsightsQuery
          GetPurchaseHistoryQuery
          GetSeniorAlertsQuery

Consent validation: RecordLocation and RecordPurchase commands check SharesLocationData/SharesPurchaseData before writing. If consent is missing, request still succeeds (204), but data is not saved.


Database schema

Users
|-- Id (PK, UUID)
|-- DeviceId (UNIQUE)
|-- IsSenior
|-- GuardianUserId (FK -> Users.Id, nullable)
|-- SharesLocationData
\-- SharesPurchaseData

LocationEvents
|-- Id (PK)
|-- UserId (FK -> Users.Id)   <- INDEX (UserId, RecordedAt)
|-- Latitude / Longitude
|-- RecordedAt
\-- PlaceCategory (nullable)

Purchases
|-- Id (PK)
|-- UserId (FK -> Users.Id)
|-- ProductName / Category / Amount
|-- PurchasedAt
|-- EstimatedDaysUntilEmpty (nullable)
\-- ExpectedEmptyDate (computed, nullable)

SeniorAlerts
|-- Id (PK)
|-- SeniorUserId (FK -> Users.Id)
|-- GuardianUserId (FK -> Users.Id, nullable)
|-- Type (enum)
|-- Message
|-- TriggeredAt
\-- IsAcknowledged

Required software

Program Version Link
Docker Desktop latest https://www.docker.com/products/docker-desktop
Node.js >= 22.11.0 https://nodejs.org
Android Studio (Android only) latest https://developer.android.com/studio
Xcode (iOS only, macOS) latest App Store
JDK 17 (Android only) 17 https://adoptium.net
.NET SDK 10 (optional - only for backend without Docker) 10.0 https://dot.net

Windows: iOS is not supported - Android only.


1. Backend + Database (Docker)

Start

# clone repo and enter root folder
cd "Programowanie AI"

# build and run API + SQL Server
docker-compose up --build

This starts:

  • API at http://localhost:8080
  • SQL Server 2022 on port 1433

Database (SasiedzkAI) is created automatically on first start.

Health check

GET http://localhost:8080/api/users/{deviceId}

Stop

docker-compose down

Database credentials (debug only)

Parameter Value
Server localhost,1433
Database SasiedzkAI
Login sa
Password SasiedzkAI_Pass1!

2. Mobile app (React Native)

Install dependencies

cd SasiedzkAIMobile
npm install

Android

Prerequisites

  1. Install Android Studio and in SDK Manager install:
    • Android SDK Platform 34 (or newer)
    • Android SDK Build-Tools
    • Android Emulator
  2. Create a virtual device in AVD Manager (Device Manager -> Create Device)
  3. Set environment variables (add to ~/.bashrc or Windows system variables):
ANDROID_HOME=C:\Users\<YourUser>\AppData\Local\Android\Sdk
PATH=$PATH:$ANDROID_HOME\emulator
PATH=$PATH:$ANDROID_HOME\platform-tools

Start emulator

Start your selected emulator from Android Studio (AVD Manager) or via terminal:

emulator -avd <emulator_name>

Run app

# in SasiedzkAIMobile folder
npx react-native run-android

iOS (macOS only)

# install CocoaPods (one-time)
sudo gem install cocoapods

# install iOS dependencies
cd ios && pod install && cd ..

# run app
npx react-native run-ios

Metro bundler (manual, optional)

npx react-native start

3. Mobile -> API connection setup

File: SasiedzkAIMobile/src/api/client.ts - line 1

Default URL: http://127.0.0.1:8080/api

Environment URL
Android Emulator http://10.0.2.2:8080/api
Physical device (USB) http://<COMPUTER_IP>:8080/api
iOS Simulator http://127.0.0.1:8080/api

For Android Emulator, change 127.0.0.1 to 10.0.2.2 - this is a special address mapped by emulator to computer localhost.


4. Recommended testing setup (Senior + Guardian at the same time)

App supports two roles: Senior and Guardian. To test both simultaneously:

Role Device Why
Senior Physical Android phone (USB) Native GPS and reliable background location
Guardian Android Emulator (Android Studio) No GPS needed, convenient desktop preview

API URL setup per role

Senior (physical phone with adb reverse) - after adb reverse, you can use same URL as default:

const BASE_URL = 'http://127.0.0.1:8080/api'; // works thanks to adb reverse

Alternative without adb reverse - use your computer LAN IP:

const BASE_URL = 'http://192.168.1.XXX:8080/api'; // check via: ipconfig

Guardian (emulator) - set BASE_URL to emulator special address:

const BASE_URL = 'http://10.0.2.2:8080/api';

Both devices must target the same backend (Docker). Physical phone connects via Wi-Fi, emulator via Android virtual network bridge.

Connect phone over USB

  1. Enable Developer options on phone (Settings -> About phone -> tap build number 7 times)
  2. Enable USB debugging
  3. Connect phone via USB cable and accept trust prompt
  4. Verify device detection:
adb devices
# sample output:
# List of devices attached
# R5CT21ABXYZ     device       <- phone ID
# emulator-5554   device       <- emulator (if running)
  1. Redirect port 8080 from phone to computer localhost (adb reverse):
adb -s <PHONE_ID> reverse tcp:8080 tcp:8080
# e.g. adb -s R5CT21ABXYZ reverse tcp:8080 tcp:8080

Why needed? Without adb reverse, physical phone cannot reach API running on computer localhost. This command creates a tunnel: request to 127.0.0.1:8080 on phone reaches port 8080 on computer (where Docker runs).

After configuring adb reverse, you can use http://127.0.0.1:8080/api for both devices - no need to type computer IP.

  1. Run app on phone:
npx react-native run-android

5. Startup order (from scratch)

1. Start Docker Desktop
2. docker-compose up --build            <- backend + database

3. Configure BASE_URL in src/api/client.ts:
   - physical phone: http://192.168.1.XXX:8080/api
   - emulator:       http://10.0.2.2:8080/api

4. Connect phone (Senior) via USB with USB debugging enabled
5. Start Android Studio emulator (Guardian)

6. (new terminal) cd SasiedzkAIMobile && npm install
7. npx react-native run-android         <- builds and installs on both devices

Project structure

Programowanie AI/
|-- src/
|   |-- SasiedzkAI.API/             API (controllers, configuration)
|   |-- SasiedzkAI.Application/     Business logic (CQRS)
|   |-- SasiedzkAI.Domain/          Domain entities
|   \-- SasiedzkAI.Infrastructure/  EF Core, repositories, background services
|-- SasiedzkAIMobile/               React Native app
|-- docker-compose.yml
|-- Dockerfile
\-- SasiedzkAI.slnx

Ports

Service Port
API 8080
SQL Server 1433

API endpoints

Resource Path
Users /api/users
Location /api/location
Purchases /api/purchases
Senior alerts /api/senioralerts

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages