# Fleet BLE Monitor Backend Contract Draft

Черновик backend-модели, GraphQL-like API и use-cases для `fleet-ble-monitor`.

Документ написан в формате, близком к GraphQL Docs: у сущности сразу перечислены поля, рядом указаны связанные enum/input/page-типы. Это не финальная схема, а база для обсуждения со старшим.

## Общие скаляры

```graphql
scalar DateTime
scalar Date
```

`DateTime` — ISO-строка даты и времени.

`Date` — календарная дата в формате `YYYY-MM-DD`.

## User

Пользователь системы. Используется для авторизации, прав доступа и как получатель отчетов.

```graphql
type User {
  id: ID!
  lastName: String!
  firstName: String!
  middleName: String
  email: String!
  login: String!
  role: UserRole!
  createdAt: DateTime!
  updatedAt: DateTime
}
```

### UserRole

Роль пользователя.

```graphql
enum UserRole {
  ADMIN
  USER
}
```

`ADMIN` — администратор.

`USER` — пользователь.

### UsersPage

Страница пользователей для таблицы.

```graphql
type UsersPage {
  totalPages: Int!
  totalElements: Int!
  page: [User!]!
}
```

### UserSortField

Поле сортировки пользователей.

```graphql
enum UserSortField {
  ID
  NAME
  DATE
}
```

### SortDirection

Общее направление сортировки.

```graphql
enum SortDirection {
  ASC
  DESC
}
```

### LoginPayload

```graphql
input LoginPayload {
  loginOrEmail: String!
  password: String!
}
```

### CreateUserPayload

```graphql
input CreateUserPayload {
  lastName: String!
  firstName: String!
  middleName: String
  email: String!
  login: String!
  password: String!
  role: UserRole!
}
```

### UpdateUserPayload

```graphql
input UpdateUserPayload {
  lastName: String
  firstName: String
  middleName: String
  email: String
  login: String
  password: String
  role: UserRole
}
```

## Machine

АТТ / единица техники. Основная бизнес-сущность мониторинга.

```graphql
type Machine {
  id: ID!
  name: String!
  licensePlate: String!
  type: MachineType!
  department: Department!
  description: String
  beacon: Beacon
  lastKnownState: MachineStateCode!
  lastDataReceivedAt: DateTime
  createdAt: DateTime!
  updatedAt: DateTime!
}
```

### MachineType

Справочник типов АТТ.

```graphql
type MachineType {
  id: ID!
  name: String!
  description: String
  createdAt: DateTime!
  updatedAt: DateTime!
}
```

Примеры: каток, самосвал, погрузчик, экскаватор.

### Department

Справочник подразделений, участков или объектов.

```graphql
type Department {
  id: ID!
  name: String!
  description: String
  createdAt: DateTime!
  updatedAt: DateTime!
}
```

### MachineStateCode

Последнее известное базовое состояние АТТ.

```graphql
enum MachineStateCode {
  IDLE
  ENGINE_ON
  MOVING
  NO_DATA
}
```

`IDLE` — стоит.

`ENGINE_ON` — заведена.

`MOVING` — в движении.

`NO_DATA` — нет данных.

### MachinesPage

```graphql
type MachinesPage {
  totalPages: Int!
  totalElements: Int!
  page: [Machine!]!
}
```

### MachineSortField

```graphql
enum MachineSortField {
  ID
  NAME
  DATE
}
```

### CreateMachinePayload

```graphql
input CreateMachinePayload {
  name: String!
  licensePlate: String!
  typeId: ID!
  departmentId: ID!
  description: String
}
```

### UpdateMachinePayload

```graphql
input UpdateMachinePayload {
  name: String
  licensePlate: String
  typeId: ID
  departmentId: ID
  description: String
}
```

## Beacon

BLE-маяк. Хранит идентификатор, MAC-адрес, статус привязки, последнюю связь.

```graphql
type Beacon {
  id: ID!
  identifier: String!
  macAddress: String!
  status: BeaconStatus!
  boundMachine: Machine
  lastSeenAt: DateTime
  createdAt: DateTime!
  updatedAt: DateTime!
}
```

### BeaconStatus

```graphql
enum BeaconStatus {
  FREE
  BOUND
}
```

`FREE` — маяк свободен.

`BOUND` — маяк привязан к АТТ.

### BeaconTelemetryPacket

Сырое сообщение от маяка.

```graphql
type BeaconTelemetryPacket {
  id: ID!
  beacon: Beacon!
  machine: Machine
  mac: String!
  vibration: Float!
  duration: Int!
  timeMillis: Long!
  receivedAt: DateTime!
}
```

### BeaconTelemetryPayload

Payload входящего сообщения от маяка.

```graphql
input BeaconTelemetryPayload {
  mac: String!
  vibration: Float!
  duration: Int!
  timeMillis: Long!
}
```

`duration` — длительность замера/пакета в секундах.

`timeMillis` — время события на стороне маяка или шлюза.

## MachineStateDefinition

Справочник состояний АТТ без привязки к конкретным значениям акселерометра.

```graphql
type MachineStateDefinition {
  id: ID!
  name: String!
  icon: String!
  color: String!
  description: String
  isSystem: Boolean!
  createdAt: DateTime!
  updatedAt: DateTime!
}
```

Примеры:

- `Стоит`
- `Заведена`
- `В движении`
- `Работа с нагрузкой`
- `Нет данных`

`Нет данных` — системное состояние, его нельзя удалять и нельзя использовать в ручном правиле калибровки.

### CreateMachineStateDefinitionPayload

```graphql
input CreateMachineStateDefinitionPayload {
  name: String!
  icon: String!
  color: String!
  description: String
}
```

### UpdateMachineStateDefinitionPayload

```graphql
input UpdateMachineStateDefinitionPayload {
  name: String
  icon: String
  color: String
  description: String
}
```

## MachineCalibrationRule

Правило калибровки для конкретной АТТ. Связывает диапазон значений вибрации с состоянием из справочника.

```graphql
type MachineCalibrationRule {
  id: ID!
  machine: Machine!
  stateDefinition: MachineStateDefinition!
  vibrationMin: Float!
  vibrationMax: Float!
  createdAt: DateTime!
  updatedAt: DateTime!
}
```

### CreateMachineCalibrationRulePayload

```graphql
input CreateMachineCalibrationRulePayload {
  machineId: ID!
  stateDefinitionId: ID!
  vibrationMin: Float!
  vibrationMax: Float!
}
```

### UpdateMachineCalibrationRulePayload

```graphql
input UpdateMachineCalibrationRulePayload {
  stateDefinitionId: ID
  vibrationMin: Float
  vibrationMax: Float
}
```

## MachineStateInterval

Распознанный интервал состояния АТТ.

```graphql
type MachineStateInterval {
  id: ID!
  machine: Machine!
  stateDefinition: MachineStateDefinition
  stateCode: MachineStateCode
  startedAt: DateTime!
  endedAt: DateTime
  durationSeconds: Int!
  vibrationMin: Float
  vibrationMax: Float
  vibrationAvg: Float
  sampleCount: Int!
  isNoData: Boolean!
}
```

Если `stateDefinition` равен `null`, участок считается нераспознанным.

Если `isNoData = true`, участок относится к системному состоянию `Нет данных`.

### MachineStateHistoryPage

```graphql
type MachineStateHistoryPage {
  totalPages: Int!
  totalElements: Int!
  page: [MachineStateInterval!]!
}
```

## MachineUsageSummary

Краткая статистика по АТТ за период.

```graphql
type MachineUsageSummary {
  machine: Machine!
  periodStart: DateTime!
  periodEnd: DateTime!
  movingSeconds: Int!
  engineOnSeconds: Int!
  idleSeconds: Int!
  noDataSeconds: Int!
  stateChangesCount: Int!
  utilizationPercent: Int!
}
```

## FleetReportSchedule

Настройка ежедневной рассылки отчета по парку АТТ.

```graphql
type FleetReportSchedule {
  id: ID!
  name: String!
  recipients: [User!]!
  reportFormat: ReportFormat!
  sendTime: String!
  period: ReportPeriod!
  timezone: String!
  enabled: Boolean!
  createdAt: DateTime!
  updatedAt: DateTime!
}
```

### ReportFormat

Формат отчета.

```graphql
enum ReportFormat {
  PDF
  XLSX
}
```

### ReportPeriod

Период отчета.

```graphql
enum ReportPeriod {
  PREVIOUS_DAY
}
```

`PREVIOUS_DAY` — предыдущий календарный день относительно timezone расписания.

### CreateFleetReportSchedulePayload

```graphql
input CreateFleetReportSchedulePayload {
  recipientUserIds: [ID!]!
  reportFormat: ReportFormat!
  sendTime: String!
}
```

### UpdateFleetReportSchedulePayload

```graphql
input UpdateFleetReportSchedulePayload {
  recipientUserIds: [ID!]
  reportFormat: ReportFormat
  sendTime: String
  enabled: Boolean
}
```

## FleetReportDelivery

История конкретной попытки отправки отчета.

```graphql
type FleetReportDelivery {
  id: ID!
  schedule: FleetReportSchedule
  reportDate: Date!
  reportFormat: ReportFormat!
  recipients: [User!]!
  status: ReportDeliveryStatus!
  fileName: String
  errorMessage: String
  createdAt: DateTime!
  sentAt: DateTime
}
```

### ReportDeliveryStatus

```graphql
enum ReportDeliveryStatus {
  CREATED
  GENERATING
  SENT
  FAILED
}
```

`CREATED` — задача создана.

`GENERATING` — отчет формируется.

`SENT` — отчет отправлен.

`FAILED` — ошибка генерации или отправки.

## FleetDailyReport

Доменная модель дневного отчета по парку АТТ.

```graphql
type FleetDailyReport {
  date: Date!
  generatedAt: DateTime!
  summary: FleetDailyReportSummary!
  machines: [FleetDailyReportMachineRow!]!
  history: [FleetDailyReportHistoryRow!]!
}
```

### FleetDailyReportSummary

```graphql
type FleetDailyReportSummary {
  totalMachines: Int!
  boundMachines: Int!
  unboundMachines: Int!
  machinesWithMovement: Int!
  machinesWithoutData: Int!
  movingSeconds: Int!
  engineOnSeconds: Int!
  idleSeconds: Int!
  noDataSeconds: Int!
  stateChangesCount: Int!
}
```

### FleetDailyReportMachineRow

```graphql
type FleetDailyReportMachineRow {
  machine: Machine!
  movingSeconds: Int!
  engineOnSeconds: Int!
  idleSeconds: Int!
  noDataSeconds: Int!
  stateChangesCount: Int!
  utilizationPercent: Int!
}
```

### FleetDailyReportHistoryRow

```graphql
type FleetDailyReportHistoryRow {
  machine: Machine!
  startedAt: DateTime!
  endedAt: DateTime!
  stateDefinition: MachineStateDefinition
  stateCode: MachineStateCode
  durationSeconds: Int!
}
```

### GenerateFleetDailyReportPayload

```graphql
input GenerateFleetDailyReportPayload {
  date: Date!
  format: ReportFormat!
}
```

### ReportFile

Ссылка на готовый файл отчета.

```graphql
type ReportFile {
  fileName: String!
  mimeType: String!
  downloadUrl: String!
  expiresAt: DateTime
}
```

Физическая отдача файла может быть не через GraphQL, а через REST endpoint или object storage URL. GraphQL в таком случае возвращает `downloadUrl`.

## Query

```graphql
type Query {
  currentUser: User

  getUsers(
    page: Int!
    pageSize: Int
    query: String
    roles: [UserRole!]
    sortDirection: SortDirection!
    sortField: UserSortField!
  ): UsersPage!

  getUser(id: ID!): User

  getMachines(
    page: Int!
    pageSize: Int
    query: String
    sortDirection: SortDirection!
    sortField: MachineSortField!
  ): MachinesPage!

  getMachine(id: ID!): Machine

  getMachineTypes: [MachineType!]!

  getDepartments: [Department!]!

  getBeacons: [Beacon!]!

  getFreeBeacons: [Beacon!]!

  getBeaconTelemetry(
    beaconId: ID
    machineId: ID
    startDate: DateTime!
    endDate: DateTime!
  ): [BeaconTelemetryPacket!]!

  getMachineStateDefinitions: [MachineStateDefinition!]!

  getMachineCalibrationRules(machineId: ID!): [MachineCalibrationRule!]!

  getMachineStateHistory(
    machineId: ID!
    startDate: DateTime!
    endDate: DateTime!
    page: Int
    pageSize: Int
  ): MachineStateHistoryPage!

  getMachineUsageSummary(
    machineId: ID!
    startDate: DateTime!
    endDate: DateTime!
  ): MachineUsageSummary!

  getUnrecognizedTelemetryIntervals(
    machineId: ID!
    startDate: DateTime!
    endDate: DateTime!
  ): [MachineStateInterval!]!

  getFleetReportSchedules: [FleetReportSchedule!]!

  getFleetReportDeliveries(
    scheduleId: ID
    dateFrom: Date
    dateTo: Date
  ): [FleetReportDelivery!]!

  getFleetDailyReport(date: Date!): FleetDailyReport!

  createFleetDailyReportDownloadUrl(
    payload: GenerateFleetDailyReportPayload!
  ): ReportFile!
}
```

## Mutation

```graphql
type Mutation {
  login(payload: LoginPayload!): User!

  logout: Boolean!

  createUser(payload: CreateUserPayload!): User!

  updateUser(
    id: ID!
    payload: UpdateUserPayload!
  ): User!

  deleteUser(id: ID!): Boolean!

  createMachine(payload: CreateMachinePayload!): Machine!

  updateMachine(
    id: ID!
    payload: UpdateMachinePayload!
  ): Machine!

  deleteMachine(id: ID!): Boolean!

  bindBeaconToMachine(
    machineId: ID!
    beaconId: ID!
  ): Machine!

  unbindBeaconFromMachine(machineId: ID!): Machine!

  registerBeaconTelemetryPacket(
    payload: BeaconTelemetryPayload!
  ): Boolean!

  createMachineType(name: String!): MachineType!

  updateMachineType(
    id: ID!
    name: String!
  ): MachineType!

  deleteMachineType(id: ID!): Boolean!

  createDepartment(name: String!): Department!

  updateDepartment(
    id: ID!
    name: String!
  ): Department!

  deleteDepartment(id: ID!): Boolean!

  createMachineStateDefinition(
    payload: CreateMachineStateDefinitionPayload!
  ): MachineStateDefinition!

  updateMachineStateDefinition(
    id: ID!
    payload: UpdateMachineStateDefinitionPayload!
  ): MachineStateDefinition!

  deleteMachineStateDefinition(id: ID!): Boolean!

  createMachineCalibrationRule(
    payload: CreateMachineCalibrationRulePayload!
  ): MachineCalibrationRule!

  updateMachineCalibrationRule(
    id: ID!
    payload: UpdateMachineCalibrationRulePayload!
  ): MachineCalibrationRule!

  deleteMachineCalibrationRule(id: ID!): Boolean!

  recognizeMachineStates(
    machineId: ID!
    startDate: DateTime!
    endDate: DateTime!
  ): [MachineStateInterval!]!

  createFleetReportSchedule(
    payload: CreateFleetReportSchedulePayload!
  ): FleetReportSchedule!

  updateFleetReportSchedule(
    id: ID!
    payload: UpdateFleetReportSchedulePayload!
  ): FleetReportSchedule!

  deleteFleetReportSchedule(id: ID!): Boolean!

  sendFleetDailyReport(
    scheduleId: ID!
    reportDate: Date!
  ): FleetReportDelivery!
}
```

## Use-Cases

### Пользователи

`LoginUserUseCase` — авторизует пользователя по логину/email и паролю.

`GetCurrentUserUseCase` — возвращает текущего авторизованного пользователя.

`GetUsersUseCase` — возвращает список пользователей с поиском, сортировкой и пагинацией.

`CreateUserUseCase` — создает пользователя.

`UpdateUserUseCase` — редактирует данные пользователя.

`DeleteUserUseCase` — удаляет пользователя или блокирует, если будет выбран soft-delete подход.

### АТТ

`GetMachinesUseCase` — возвращает список АТТ с поиском, сортировкой и пагинацией.

`GetMachineByIdUseCase` — возвращает карточку конкретной АТТ.

`CreateMachineUseCase` — создает новую АТТ.

`UpdateMachineUseCase` — редактирует данные АТТ.

`DeleteMachineUseCase` — удаляет АТТ или переводит в архив.

`BindBeaconToMachineUseCase` — привязывает свободный маяк к АТТ.

`UnbindBeaconFromMachineUseCase` — отвязывает маяк от АТТ.

### Маяки и телеметрия

`RegisterBeaconTelemetryPacketUseCase` — принимает сырое сообщение от маяка и сохраняет его.

`ResolveBeaconByMacUseCase` — находит маяк по MAC-адресу из входящего пакета.

`AttachTelemetryPacketToMachineUseCase` — определяет, к какой АТТ относится пакет, если маяк привязан.

`GetBeaconTelemetryUseCase` — возвращает сырые показания маяка за период.

`GetFreeBeaconsUseCase` — возвращает список свободных маяков.

### Калибровка

`GetMachineStateDefinitionsUseCase` — возвращает справочник состояний АТТ.

`CreateMachineStateDefinitionUseCase` — создает новое состояние в справочнике.

`UpdateMachineStateDefinitionUseCase` — редактирует состояние справочника.

`DeleteMachineStateDefinitionUseCase` — удаляет состояние, если оно не используется правилами.

`GetMachineCalibrationRulesUseCase` — возвращает правила калибровки конкретной АТТ.

`CreateMachineCalibrationRuleUseCase` — создает правило калибровки для АТТ.

`UpdateMachineCalibrationRuleUseCase` — редактирует правило калибровки.

`DeleteMachineCalibrationRuleUseCase` — удаляет правило калибровки.

`ValidateMachineCalibrationRuleUseCase` — проверяет правило на конфликты с уже существующими диапазонами.

`RecognizeMachineStatesUseCase` — по сырым данным акселерометра и правилам калибровки строит интервалы состояний.

`GetUnrecognizedTelemetryIntervalsUseCase` — возвращает участки телеметрии, которые не попали ни под одно правило.

### История и аналитика

`GetMachineStateHistoryUseCase` — возвращает историю состояний АТТ за период.

`GetMachineUsageSummaryUseCase` — собирает краткую статистику по АТТ за период.

`GetMachineDailyUsageStatsUseCase` — возвращает дневную статистику по АТТ для графиков.

`AggregateMachineStateIntervalsUseCase` — агрегирует интервалы состояний из телеметрии.

### Отчеты

`GetFleetReportSchedulesUseCase` — возвращает настройки рассылок отчетов.

`CreateFleetReportScheduleUseCase` — создает настройку ежедневной рассылки отчета.

`UpdateFleetReportScheduleUseCase` — редактирует настройку рассылки.

`DeleteFleetReportScheduleUseCase` — удаляет настройку рассылки.

`GenerateFleetDailyReportUseCase` — собирает дневной отчет по парку АТТ за выбранную дату.

`ExportFleetDailyReportToPdfUseCase` — формирует PDF-файл дневного отчета.

`ExportFleetDailyReportToXlsxUseCase` — формирует Excel-файл дневного отчета.

`DownloadFleetDailyReportUseCase` — отдает файл отчета пользователю по запросу из интерфейса.

`RunScheduledFleetReportsUseCase` — периодически запускает активные расписания рассылок.

`SendFleetDailyReportUseCase` — генерирует отчет и отправляет его выбранным пользователям.

`CreateFleetReportDeliveryUseCase` — создает запись о попытке отправки отчета.

`MarkFleetReportDeliveryAsSentUseCase` — помечает отправку как успешную.

`MarkFleetReportDeliveryAsFailedUseCase` — сохраняет ошибку отправки.

## Доменные сервисы

`MachineStateRecognitionService` — определяет состояние АТТ по телеметрии и правилам калибровки.

`MachineAnalyticsService` — считает агрегаты: время в движении, заведена, стояла, нет данных, смены состояний.

`FleetDailyReportBuilder` — собирает доменную модель дневного отчета.

`FleetReportExporter` — общий интерфейс для экспорта отчета в файл.

`PdfFleetReportExporter` — экспортирует отчет в PDF.

`XlsxFleetReportExporter` — экспортирует отчет в Excel.

`MailService` — отправляет письма с вложениями.

`ReportScheduler` — запускает ежедневные рассылки по расписанию.

