Skip to content

April Robots Backends

Online Backend

We use a layered architecture design for April Robots Online backend (Online backend). We have Presentation –> Service –> Data Access layers structure:

  • Presentation – routers;
    • process requests (body and query params)
    • set access levels
    • return response with proper status
    • provide endpoint documentation
    • basic validation (via Pydantic)
  • Service – services;
    • do the stuff with the data using Data Access layer
    • complex validation, raising HTTPException-s
  • Data Access – repositories (repository pattern);
    • all operations with the database used by services
    • can also raise HTTPExceptions like 404

Online backend is built with FastAPI and uses MongoDB. There are different conventions for writing code in Python/FastAPI covered here.

Online backend repository is “modular” – i.e. the app is divided into self-contained modules with their own router(s), service, schemas, repository(ies), models, etc. A generic structure of a module is:

  • init.py – module initialization.
  • config.py – module-specific configs (class Config(BaseSettings): ...).
  • constants.py – constants and enums not appropriate for the module config.
  • dependencies – SvcDep and other things with FastAPI dependencies.
  • models.py – app models (correspond to DB table/file) and necessary schemas for models with complex fields.
  • repository – repository-level logic, i.e. database queries. Repositories inherit from the base class BaseRepository[T].
  • router.py – routes and routers.
  • schemas.py – schemas defining data input and output formats for endpoints.
  • service.py – service-level logic. Defined as a general class Service: ....
  • utils.py – module-specific utility functions.

When creating a new module, use the structure above and omit files that are not applicable to your module. The GitHub repository contains a detailed explanation of the structure of the backend.

Offline Backend

Offline Backend is very similar to Online Backend. However, we use DictDataBase library for file-based database because it is easier than shipping Mongo. As such, repositories of Offline Backend are completely different and rely on DictDataBase API. The GitHub repository contains a detailed explanation of the structure of the backend.

In addition, Offline Backend is structured to keep assets (images, scripts) downloaded from Online Backend during the synchronization flow.

Crucially, Offline Backend also contains everything needed to build (compile) the Offline App into a Windows executable, including Electron and installer/updater scripts.

Entity relationship diagram:

    ---
config:
  layout: elk
---

erDiagram
    CHILD_MODEL {
      string _id PK "Primary Key"
      string name "First Name"
      string surname "Last Name"
      bool is_active "True, False"
      date birthday_date "Date of Birth"
      enum[string] gender "male, female, other"
      list[json] skills "Array of SKILL_EMBEDDED"
      list[json] radar_graphs "Array of RADAR_GRAPH_EMBEDDED"
      list[string] session_ids "References SESSION_MODEL._id"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
      %%   updated at
      %%   center_ids
    }

    %% ChildCenters
    %% child_id
    %% center_ids

    SKILL_EMBEDDED {
      string _id "Refers to SKILL_MODEL._id"
      int value "Range: 1-100"
    }


    RADAR_GRAPH_EMBEDDED {
      datetime created_at "Graph creation date and time"
      list[json] skills "Array of SKILL_GRAPH_EMBEDDED"
    }

    SKILL_GRAPH_EMBEDDED {
      string _id "Refers to SKILL_MODEL._id"
      int value "Range: 1-10"
    }

    SESSION_MODEL {
      string _id PK "Primary Key"
      string child_id "FK CHILD_MODEL._id"
      datetime started_at "Session Start Time"
      nullable[datetime] ended_at "Session End Time"
      list[json] assessment_questions "Array of ASSESSMENT_QUESTION_COLLECTED"
      list[json] performed_actions "Array of PERFORMED_ACTION"
      list[json] robot_sessions "Array of ROBOT_SESSION"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    ASSESSMENT_QUESTION_COLLECTED {
      string _id "Refers to ASSESSMENT_QUESTION_MODEL._id"
      string question "Localized question"
      nullable[int] answer "Range: 1-10"
    }

    PERFORMED_ACTION {
      string action_id "Refers to ACTION_MODEL._id"
      datetime requested_at "Action Start Requested Time"
      enum[string] language "kk, en, ru"
      list[json] specialist_grades "Array of SPECIALIST_GRADE_COLLECTED"
      nullable[enum[string]] status "queued, running, completed, failed, stopped, none"
      nullable[datetime] started_at "Action Start Time"
      nullable[datetime] ended_at "Action End Time"
      nullable[list[json]] robot_metrics "Array of ROBOT_METRIC_COLLECTED"
      nullable[json] metrics_raw_data "Dictionary of raw metrics data"
    }

    ROBOT_SESSION {
      string _id PK "Primary Key"
      datetime started_at "Session Start Time"
      nullable[datetime] ended_at "Session End Time"
      enum[string] robot "nao, furhat"
    }

    ROBOT_METRIC_COLLECTED {
      string _id "Refers to ROBOT_METRIC_MODEL._id"
      float value "Collected value, >= 0"
    %%   float max_value "Range: 1-10"
      json translations "Copied translations of { name, unit, description }"
      float normalized "Normalized value"
      enum[string] status "approved, disapproved, n/a"
    }

    SPECIALIST_GRADE_COLLECTED {
      string skill_id "FK SKILL_MODEL._id"
      nullable[int] value "Grade value; range: 0-10"
      json translations "Copied translations of { name, description }"
    }

    AREA_MODEL {
      string _id PK "Primary Key"
      slug string "Slug string"
      nullable[string] image_path "Image path"
      json translations "Localized translations of { name, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    DOMAIN_MODEL {
      string _id PK "Primary Key"
      slug string "Slug string"
      list[string] area_ids "FK AREA_MODEL._id"
      nullable[string] image_path "Image path"
      json translations "Localized translations of { name, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    SKILL_MODEL {
      string _id PK "Primary Key"
      slug string "Slug string"
      list[string] domain_id "FK DOMAIN_MODEL._id"
      json translations "Localized translations of { name, description }"
      list[json] robot_metrics "Array of ROBOT_METRIC_CONFIG"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    ROBOT_METRIC_CONFIG {
      string _id "Refers to ROBOT_METRIC_MODEL._id"
      json translations "Copied translations of { name, unit, description }"
      float weight "Weight of the metric"
      float threshold "Threshold value"
    }

    ACTION_MODEL {
      string _id PK "Primary Key"
      string category_id "FK CATEGORY_MODEL.id"
      enum[string] robot_type "nao, furhat"
      list[string] skill_ids "FK SKILL_MODEL._id"
      nullable[string] image_path "Nullable image path"
      list[string] specialist_grades "Array of SPECIALIST_GRADE_INLINE"
      json translations "Localized translations of { name, description, script_path, max_metrics }; <br> max_metrics is an object of type { ROBOT_METRIC_MODEL._id => max_val (float) }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    CATEGORY_MODEL {
      string _id PK "Primary Key"
      string slug "Unique URL-friendly string"
      enum[string] robot_type "nao, furhat"
      nullable[string] image_path "Nullable image path"
      json translations "Localized translations of { name, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    SPECIALIST_GRADE_INLINE {
      string skill_id "FK SKILL_MODEL._id"
      int max_val "Range: 1-10"
      enum[string] scale_type "numeric, boolean"
      json translation "Localized translations of { name, description }"
    }


    ROBOT_METRIC_MODEL {
      string _id PK "Primary Key"
      string slug "Unique slug"
      json translations "Localized translations of { name, unit, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    ASSESSMENT_QUESTION_MODEL {
      string _id PK "Primary Key"
      json translations "Localized translations of { question, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    %% Embedding Relationships (solid lines, 'includes')
    CHILD_MODEL ||--o{ SKILL_EMBEDDED : includes
    CHILD_MODEL ||--o{ RADAR_GRAPH_EMBEDDED : includes
    RADAR_GRAPH_EMBEDDED ||--o{ SKILL_GRAPH_EMBEDDED : includes
    SESSION_MODEL ||--o{ ROBOT_SESSION : includes
    SESSION_MODEL ||--o{ PERFORMED_ACTION : includes
    SESSION_MODEL ||--o{ ASSESSMENT_QUESTION_COLLECTED : includes
    ACTION_MODEL ||--o{ SPECIALIST_GRADE_INLINE : includes
    SKILL_MODEL ||--o{ ROBOT_METRIC_CONFIG : includes
    PERFORMED_ACTION ||--o{ ROBOT_METRIC_COLLECTED : includes
    PERFORMED_ACTION ||--o{ SPECIALIST_GRADE_COLLECTED : includes

    %% Reference Relationships (dotted lines with arrows, 'references')
    SESSION_MODEL }|..|| CHILD_MODEL : references
    CHILD_MODEL ||..|{ SESSION_MODEL : references
    SKILL_MODEL }|..|| DOMAIN_MODEL : references
    DOMAIN_MODEL }|..|{ AREA_MODEL : references
    SPECIALIST_GRADE_COLLECTED }|..|| SKILL_MODEL : references
    SPECIALIST_GRADE_INLINE }|..|| SKILL_MODEL : references
    ACTION_MODEL }|..|| CATEGORY_MODEL : references
    ACTION_MODEL }|..|{ SKILL_MODEL : references
    PERFORMED_ACTION }|..|| ACTION_MODEL : references
    SKILL_EMBEDDED ||..|| SKILL_MODEL : references
    SKILL_GRAPH_EMBEDDED ||..|| SKILL_MODEL : references
    ROBOT_METRIC_COLLECTED ||..|| ROBOT_METRIC_MODEL : references
    ROBOT_METRIC_CONFIG ||..|| ROBOT_METRIC_MODEL: references
    ASSESSMENT_QUESTION_COLLECTED ||..|| ASSESSMENT_QUESTION_MODEL : references

    %% SESSION_MODEL ||..|| ASSESSMENT_QUESTION_MODEL : uses
    %% add metrics embedded and grades embedded
  

Source code:

---
config:
  layout: elk
---

erDiagram
    CHILD_MODEL {
      string _id PK "Primary Key"
      string name "First Name"
      string surname "Last Name"
      bool is_active "True, False"
      date birthday_date "Date of Birth"
      enum[string] gender "male, female, other"
      list[json] skills "Array of SKILL_EMBEDDED"
      list[json] radar_graphs "Array of RADAR_GRAPH_EMBEDDED"
      list[string] session_ids "References SESSION_MODEL._id"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
      %%   updated at
      %%   center_ids
    }

    %% ChildCenters
    %% child_id
    %% center_ids

    SKILL_EMBEDDED {
      string _id "Refers to SKILL_MODEL._id"
      int value "Range: 1-100"
    }


    RADAR_GRAPH_EMBEDDED {
      datetime created_at "Graph creation date and time"
      list[json] skills "Array of SKILL_GRAPH_EMBEDDED"
    }

    SKILL_GRAPH_EMBEDDED {
      string _id "Refers to SKILL_MODEL._id"
      int value "Range: 1-10"
    }

    SESSION_MODEL {
      string _id PK "Primary Key"
      string child_id "FK CHILD_MODEL._id"
      datetime started_at "Session Start Time"
      nullable[datetime] ended_at "Session End Time"
      list[json] assessment_questions "Array of ASSESSMENT_QUESTION_COLLECTED"
      list[json] performed_actions "Array of PERFORMED_ACTION"
      list[json] robot_sessions "Array of ROBOT_SESSION"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    ASSESSMENT_QUESTION_COLLECTED {
      string _id "Refers to ASSESSMENT_QUESTION_MODEL._id"
      string question "Localized question"
      nullable[int] answer "Range: 1-10"
    }

    PERFORMED_ACTION {
      string action_id "Refers to ACTION_MODEL._id"
      datetime requested_at "Action Start Requested Time"
      enum[string] language "kk, en, ru"
      list[json] specialist_grades "Array of SPECIALIST_GRADE_COLLECTED"
      nullable[enum[string]] status "queued, running, completed, failed, stopped, none"
      nullable[datetime] started_at "Action Start Time"
      nullable[datetime] ended_at "Action End Time"
      nullable[list[json]] robot_metrics "Array of ROBOT_METRIC_COLLECTED"
      nullable[json] metrics_raw_data "Dictionary of raw metrics data"
    }

    ROBOT_SESSION {
      string _id PK "Primary Key"
      datetime started_at "Session Start Time"
      nullable[datetime] ended_at "Session End Time"
      enum[string] robot "nao, furhat"
    }

    ROBOT_METRIC_COLLECTED {
      string _id "Refers to ROBOT_METRIC_MODEL._id"
      float value "Collected value, >= 0"
    %%   float max_value "Range: 1-10"
      json translations "Copied translations of { name, unit, description }"
      float normalized "Normalized value"
      enum[string] status "approved, disapproved, n/a"
    }

    SPECIALIST_GRADE_COLLECTED {
      string skill_id "FK SKILL_MODEL._id"
      nullable[int] value "Grade value; range: 0-10"
      json translations "Copied translations of { name, description }"
    }

    AREA_MODEL {
      string _id PK "Primary Key"
      slug string "Slug string"
      nullable[string] image_path "Image path"
      json translations "Localized translations of { name, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    DOMAIN_MODEL {
      string _id PK "Primary Key"
      slug string "Slug string"
      list[string] area_ids "FK AREA_MODEL._id"
      nullable[string] image_path "Image path"
      json translations "Localized translations of { name, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    SKILL_MODEL {
      string _id PK "Primary Key"
      slug string "Slug string"
      list[string] domain_id "FK DOMAIN_MODEL._id"
      json translations "Localized translations of { name, description }"
      list[json] robot_metrics "Array of ROBOT_METRIC_CONFIG"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    ROBOT_METRIC_CONFIG {
      string _id "Refers to ROBOT_METRIC_MODEL._id"
      json translations "Copied translations of { name, unit, description }"
      float weight "Weight of the metric"
      float threshold "Threshold value"
    }

    ACTION_MODEL {
      string _id PK "Primary Key"
      string category_id "FK CATEGORY_MODEL.id"
      enum[string] robot_type "nao, furhat"
      list[string] skill_ids "FK SKILL_MODEL._id"
      nullable[string] image_path "Nullable image path"
      list[string] specialist_grades "Array of SPECIALIST_GRADE_INLINE"
      json translations "Localized translations of { name, description, script_path, max_metrics }; <br> max_metrics is an object of type { ROBOT_METRIC_MODEL._id => max_val (float) }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    CATEGORY_MODEL {
      string _id PK "Primary Key"
      string slug "Unique URL-friendly string"
      enum[string] robot_type "nao, furhat"
      nullable[string] image_path "Nullable image path"
      json translations "Localized translations of { name, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    SPECIALIST_GRADE_INLINE {
      string skill_id "FK SKILL_MODEL._id"
      int max_val "Range: 1-10"
      enum[string] scale_type "numeric, boolean"
      json translation "Localized translations of { name, description }"
    }


    ROBOT_METRIC_MODEL {
      string _id PK "Primary Key"
      string slug "Unique slug"
      json translations "Localized translations of { name, unit, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    ASSESSMENT_QUESTION_MODEL {
      string _id PK "Primary Key"
      json translations "Localized translations of { question, description }"
      datetime last_sync_at "Last Sync Time"
      datetime created_at "Creation Time"
    }

    %% Embedding Relationships (solid lines, 'includes')
    CHILD_MODEL ||--o{ SKILL_EMBEDDED : includes
    CHILD_MODEL ||--o{ RADAR_GRAPH_EMBEDDED : includes
    RADAR_GRAPH_EMBEDDED ||--o{ SKILL_GRAPH_EMBEDDED : includes
    SESSION_MODEL ||--o{ ROBOT_SESSION : includes
    SESSION_MODEL ||--o{ PERFORMED_ACTION : includes
    SESSION_MODEL ||--o{ ASSESSMENT_QUESTION_COLLECTED : includes
    ACTION_MODEL ||--o{ SPECIALIST_GRADE_INLINE : includes
    SKILL_MODEL ||--o{ ROBOT_METRIC_CONFIG : includes
    PERFORMED_ACTION ||--o{ ROBOT_METRIC_COLLECTED : includes
    PERFORMED_ACTION ||--o{ SPECIALIST_GRADE_COLLECTED : includes

    %% Reference Relationships (dotted lines with arrows, 'references')
    SESSION_MODEL }|..|| CHILD_MODEL : references
    CHILD_MODEL ||..|{ SESSION_MODEL : references
    SKILL_MODEL }|..|| DOMAIN_MODEL : references
    DOMAIN_MODEL }|..|{ AREA_MODEL : references
    SPECIALIST_GRADE_COLLECTED }|..|| SKILL_MODEL : references
    SPECIALIST_GRADE_INLINE }|..|| SKILL_MODEL : references
    ACTION_MODEL }|..|| CATEGORY_MODEL : references
    ACTION_MODEL }|..|{ SKILL_MODEL : references
    PERFORMED_ACTION }|..|| ACTION_MODEL : references
    SKILL_EMBEDDED ||..|| SKILL_MODEL : references
    SKILL_GRAPH_EMBEDDED ||..|| SKILL_MODEL : references
    ROBOT_METRIC_COLLECTED ||..|| ROBOT_METRIC_MODEL : references
    ROBOT_METRIC_CONFIG ||..|| ROBOT_METRIC_MODEL: references
    ASSESSMENT_QUESTION_COLLECTED ||..|| ASSESSMENT_QUESTION_MODEL : references

    %% SESSION_MODEL ||..|| ASSESSMENT_QUESTION_MODEL : uses
    %% add metrics embedded and grades embedded

General

Both backends rely on migrations for keeping the database schemas updated, and on versioning to ensure synchronization and release-management.