Skip to main content

Structure and Boundaries

This describes the structure you need to know when fixing the field tablet app or integrating it with other systems. The source is the plantpulse-platform/plantpulse-frontline-web repository.

Overview​

태블릿 브라우저 (PWA) frontline 컨테이너 플랫폼
┌──────────────────────┐ HTTPS ┌──────────────────────────────┐ V5 ┌──────────────┐
│ React 웹 앱 │ ──────▶ │ BFF (/api/*) │ ────▶ │ server-web │
│ 서비스워커 · 입력 큐 │ 9701 │ · 기기 토큰 · 작업자 세션 │ 토큰 │ /api/v5/... │
└──────────────────────┘ │ · 범위 검사 · 감사 로그 │ 하나 └──────────────┘
└──────────────┬───────────────┘
│ JDBC
플랫폼 DB pp · 스키마 pf
ComponentDescriptionRepository Location
WebReact 19 + TypeScript + Vite PWA. Service workers, offline input queue (IndexedDB), and last values per screen (localStorage). Graphs use ECharts 6 — as a separate chunk loaded only when a screen with a graph is opened.web/
BFFSpring Boot 4.1 WAR, running on the common Platform Tomcat 10.1 (runtime-base). The dispatcher is applied only to /api/*. Scope checks (scope/), maintenance (maintenance/), defects (defect/), and reports (floor/ReportController) are located here.src/plantpulse/frontline/
DBThe pf schema (previously frontline before 2026-10-29) of the Platform database pp, using the existing Platform DB account. Tables start with fl_. The schema is migrated via Flyway (V1~V14).src/db/migration/
Imagedocker.kopens.io/pp/plantpulse-frontline-web. Port 9701 (TLS) · 9700 (Health check only)Dockerfile

Schema History​

VersionDescription
V1~V4Operator · PIN · Device · Settings · Reason List · Shift Notes · Audit Logs, Screen Language · Theme
V5Removed CHECK IN ('ko','en') from the language field and expanded fl_operator.lang · fl_operator_revision.lang · fl_device.default_lang to VARCHAR(35) (BCP 47). Allowed languages are determined by a single list on the server, not in the schema.
V6Deleted the kiosk.micro_stop_seconds row in fl_setting. The short stop criterion is a Platform setting (old values are not migrated).
V7Assigned scope. Below.
V8Index for reading audit logs by person in page units.
V9Connection field between operator login and Platform employee (pp.mm_employee).
V10Migrated operator accounts to the Platform. Created login cache fl_account_cache and synchronization status fl_account_sync, and changed the session to point to the Platform employee ID (existing sessions are deleted — all tablets re-login after migration). Old fl_operator · fl_operator_scope are deleted in V11 after confirming migration to the Platform.
V11Delete old operator tables (fl_operator · fl_operator_scope and their history tables). Deletion occurs only in installations (fl_account_sync.imported_at) marked as migration complete; those not yet migrated are retained.
V12Equipment «Key Values» fl_key_value — up to 6 tags to be shown first in the equipment overview (tag ID and order only; values are read from V5).
V13Customer Logo fl_brand_logo — one PNG each for light and dark themes.
V14Expanded the allowance levels of the device default scope fl_device.default_scope_type to four levels: SITE · AREA · LINE · ASSET (previously LINE · ASSET). Existing rows remain.

Schema Renaming. The app performs ALTER SCHEMA frontline RENAME TO pf as a single statement only once when the frontline schema exists and pf does not, prior to Flyway. Tables, data, Flyway history, and settings (ai.url · ai.api_key) are moved without copying. Upon restart, it does nothing as pf already exists (idempotent). For new installations, Flyway creates pf. If both exist, it leaves a warning without guessing and uses pf. If the DB does not respond, the app does not start — otherwise, Flyway might create an empty pf, making data appear lost.

Operator Account Migration. Before the first synchronization after startup, fl_operator · fl_operator_scope are sent once to V5 POST /app-accounts/frontline/import. PIN hashes are moved as-is (not re-hashed). For logins not linked to an employee, the Platform creates and attaches an employee EMP_FL_<로그인 번호>. If a login number already exists on the Platform, the Platform version is kept (EXISTS). Migration is marked complete only when all rows return via CREATED · EXISTS; otherwise, they are resent in the next cycle (idempotent).

Boundaries with Platform​

The app does not generate numbers

OEE, availability, performance, quality, downtime, loss, MTBF, and MTTR are displayed as values provided by Platform V5 only. Values not provided by the Platform are left blank, shown as «No Data», or hidden; they are never filled with 0 or estimated values. The only value calculated on the screen is the display progress rate (good products ÷ planned), and graphs draw V5 values as-is, changing only the axis units. Thus, the tablet, Platform web, reports, and AI all see the same numbers.

  • Platform data is read and written only via V5. Even if they share the same pp database, the app does not read or write directly to Platform tables, nor does it keep copies of Platform data in the pf schema. Maintenance calls and defect records also have no copies in the app — the authoritative sources are V5 maintenance orders and defect records.
  • One exception — Login Cache fl_account_cache. This is maintained so the field can log in even while the Platform is restarting. See Operator Account below. PIN hashes, employee names, and employment statuses are not stored in other tables (prevented by tests).
  • No MES functions. Operator accounts consist only of login means (login number, role, PIN) and assigned scope (what they can see). Assigned scope is not an MES manpower assignment — there are no certifications, shift rosters, individual performance, or headcount aggregations. Orders are selected and then started, paused, resumed, ended, or stopped (with reason codes), and completion rates are reported based on time — issuance and editing are handled by the Platform.
  • Items stored in the pf schema include devices (including default language and screen theme), reason lists per equipment, kiosk settings, shift notes, audit logs, sessions, and the login cache (fl_account_cache · fl_account_sync). The authoritative source for operator accounts (login number, PIN hash, role, assigned scope, screen language, theme) is Platform pp.mm_app_account · pp.mm_app_account_scope.
  • When calling V5, the viewer's language is sent as Accept-Language. Stop reasons, failure modes, and defect cause names are displayed exactly as sent by V5.

Determination by Platform​

The app does not perform determinations for the screen; it uses values provided by V5. Determination codes previously in the app (plan, source, length comparison, shift window selection, and 8-hour replacement window) have been removed.

DeterminationV5 Source
Current Shift WindowGET /calendar/shift-schedule/current. The «Last 9 Hours» used when no roster exists is also provided by V5 (if a person selects 12 hours, it is widened accordingly). If V5 cannot provide a window, the card states that refusal; the app does not invent a window.
«Current Plan» Orderplanned_order_id in the same response
Stops Missing Reasonsneeds_reason for each stop
Short Stopsshort for each stop. Criterion is GET /config/PP_STOP_MICRO_STOP_SECONDS (default 60s, 0~1800, asks for all if 0).

V5 Calls by App​

ReadPurpose
GET /site · /asset/lines/by-site/… · /asset/equipments/by-line/… · /path/asset/…Scope selection (Site → Line → Equipment)
GET /asset/… · GET /site/… · GET /asset/equipments/by-line/…Interpreting assigned scope nodes (parent chain). Line plan comparisons are requested via the equipment list.
GET /asset/…/statusStatus and duration
GET /kpi/oee/status/current · /kpi/periodShift OEE, dashboard period metrics
GET /kpi/dailyDaily trends for dashboards and reports
GET /ram/breakdown?by=CAUSE · /failure-dictionary/alarm-causeFailure cause Pareto and cause names
GET /asset/…/stops · /asset/…/stop-lossTimeline, stops missing reasons, short stops, loss by stop reason
GET /asset/…/timeline/alarmsAlarms (min_priority, limit=500). BFF filters for those in progress (turned on within the last 7 days) and those ended today.
GET /stop-reasonsCause dictionary (reason buttons, stop reason list in settings)
GET /calendar/shift-schedule/currentCurrent shift window (asset_id). Response fields are from_ts · to_ts · shift_code · shift_date · from_plan · planned_order_id
GET /config/PP_STOP_MICRO_STOP_SECONDSShort stop criterion (1-minute cache)
GET /order/query · /order/current-asset · /order/… · /order/…/quantityOrder list, active order, details, quantity
GET /order/…/revisions · /order/…/progressChange history, completion rate report history. V5 provides only employee numbers, so the BFF attaches names found in the login cache as names (otherwise uses the employee number).
GET /maintenance-orders · /maintenance-orders/options · /maintenance-orders/…Maintenance board (open=true&order=asc&limit=100, scope/assignee filtering), symptom/code list, confirmation before cancellation
GET /asset/…/defects/options · /asset/…/defects · /order/…/defects · /defects/…Defect cause list, defect records for equipment/orders
GET /asset/qr/{token}QR label token → equipment. Called by BFF only; tokens are not stored in logs.
GET /order/deviation-summary · /defects/summary · /maintenance-orders/summaryReport plan comparisons, defect causes, maintenance summaries (slow read)

GET /order/query uses line_id · site_id · status=END,ABORTED · end_since · end_before · include=oee · sort · limit=500 · order_id filtering for the work order screen. The BFF maintains a 5-second cache for maintenance board reads and a 60-second shared cache for maintenance/defect options; caches are cleared upon writing.

WritePurposeHeader
PUT /asset/{asset}/stops/{started_at}/reasonRecord stop reasonIdempotency-Key
PUT /order/{order}/quantityCorrect quantity (Good/Defect total + reason)Idempotency-Key
POST /order/{order}/start · pause · resume · endOrder transition. Timestamp occurred_at (epoch ms) in bodyIf-Match (Order version)
POST /order/{order}/abortStop order. abort_code · abort_notes · occurred_atIf-Match · Idempotency-Key
POST /order/{order}/progressReport completion rate. percent_complete (0~100 integer) · note · reported_atIf-Match · Idempotency-Key
POST /asset/{asset}/maintenance-ordersCall maintenance (maintenance_kind_code: CORRECTIVE, failure_mode_code · description · caller)Idempotency-Key · X-PP-Channel: FRONTLINE
POST /maintenance-orders/{id}/transitionAcknowledge, Start Repair, Complete (status_code + action · part · failure code), Cancel (status_code: CANCELLED + cancel_reason_code). Version is in the body.Idempotency-Key · X-PP-Channel
POST /asset/{asset}/defectsRecord defectIdempotency-Key · X-PP-Channel
POST /defects/{id}/cancelCancel defect recordIf-Match · X-PP-Channel
  • Include X-PP-Performed-By: 로그인한 사번 in all writes. V5 records this as performed_by and keeps changed_by as the app account. The employee number of the person who pressed the button is included even when sent later by the offline queue.
  • Attach Idempotency-Key to writes. This prevents V5 from processing the same write twice if the offline queue resends.
  • Order transitions match versions using If-Match. If another person changed it in the meantime, 409 is returned and the screen shows «Another person changed this first».
  • Order transitions, stops, and defect records include the timestamp of the press. Even if sent later from the queue, the performance time is the press time.
  • «End previous order and start» consists of the BFF calling end and start sequentially. Separate idempotency keys are used for each half, and both halves share the same press timestamp. If the second part fails, the first part is not rolled back and the status is reported as-is.

For V5 itself, refer to the API documentation.

Entry via QR Label​

The Platform's Public Page «Open in frontline» arrives via <PP_UI_FRONTLINE_URL>/#/qr/<token>. The URL does not contain the equipment ID.

  1. The app immediately removes the token from the URL and stores it in the tab's sessionStorage (fl.qr_pending). It is not sent to the server before login.
  2. After login, the token is retrieved and sent in the body of BFF POST /api/v1/qr/resolve — placing it in the path would record it in every access log.
  3. The BFF first checks the token format (43 characters, base64url), finds the equipment via V5 GET /asset/qr/{token}, and verifies if that account/tablet can select the equipment using the same rules as scope chips.
  4. If passed, the app moves the scope to that equipment and opens «Now».
ResponseCodeScreen
400qr_tokenNot a label token
404qr_unknownToken reissued or equipment no longer exists
403scope_deniedOut of assigned scope

Operator Account V5​

Operator accounts are located in Platform V5 /api/v5/app-accounts/frontline. Only ADMIN tokens or the Platform master key can call this (since change feeds include PIN hashes).

CallPurpose
GET /app-accounts/frontlineAccount list (including employee name and employment status). Read-only «Operator» tab in app.
GET /app-accounts/frontline/{employee_id}Single record (ETag = version)
PUT /app-accounts/frontline/{employee_id}Create/Edit account (login number, role, allowance, temporary PIN)
DELETE /app-accounts/frontline/{employee_id}Delete login (employee remains). The last administrator cannot be deleted.
PUT /app-accounts/frontline/{employee_id}/scopeAssigned scope — {"all": true} or {"nodes": [...]}
POST /app-accounts/frontline/pin/verifyKeypad PIN verification. Always returns 200 and result (OK · INVALID · LOCKED · DISABLED · PIN_RESET_REQUIRED · EMPLOYEE_INACTIVE · EMPLOYEE_ON_LEAVE).
POST /app-accounts/frontline/{employee_id}/pinChange own PIN (current PIN, new PIN)
POST /app-accounts/frontline/{employee_id}/pin/resetAdmin PIN reset (temporary PIN, also unlocks)
POST /app-accounts/frontline/{employee_id}/unlockUnlock
GET /app-accounts/frontline/changesChange feed (cursor). Login cache reads this.
POST /app-accounts/frontline/importMigrate old app logins (idempotent)
  • Login. Upon reaching the Platform, the BFF calls only pin/verify. Failure counts, locks (5 min after 5 fails), and rejections for퇴직/휴직 are handled by the Platform.
  • Login Cache. AccountSync reads the change feed every 60 seconds from the cursor to synchronize fl_account_cache (snapshot includes PIN hash, employment status, and scope). The cache is read only when the Platform is unreachable. If the last successful synchronization is older than accounts.offline_days (Admin «Kiosk» screen, default 7 days, 1~30 days), offline login is rejected (fail-closed, 503 offline_login_expired).
  • Offline Failure. When verifying with the cache, incorrect PINs are counted by the app (5 min for 5 failures per person, 15 min for 20 failures per device) and are not sent to the Platform.
  • Offline Restrictions. PIN changes, screen language, and theme changes are only possible when connected to the Platform. Account editing, PIN reset, and unlocking are not available in the app (available in console «Employee»).
  • User-modifiable items are PIN (BFF PUT /v1/session/pin), screen language, theme (PUT /v1/me/preferences), and contact/photo in «My Profile» (PUT /v1/me/profile · PUT /v1/me/photo, Platform employee info).

High Availability (HA)​

PG meta synchronization for Platform HA also moves frontline data to the standby cluster.

SynchronizedNot Synchronized (per node)
Operator accounts and assigned scope pp.mm_app_account · pp.mm_app_account_scope (including PIN hash)Sessions, device registration codes, audit logs, shift notes, idempotency records
Kiosk settings pf.fl_setting, stop reason lists per equipment pf.fl_asset_reason_list, registered tablets pf.fl_deviceLogin cache fl_account_cache · fl_account_sync — each node fills its own via its Platform's change feed
AI key (ai.api_key in fl_setting) only when metasync.include.tokens=true

Platform Restart​

Platform restarts typically take 1~3 minutes, up to 10 minutes. The app does not stop during this time.

  • If the BFF cannot reach V5 or its own DB, it notifies «Currently Unavailable» via 503 and X-FL-Offline headers. This is distinguished from rejections (4xx).
  • Upon receiving this response, the web indicates a disconnection, and the screen shows the last values with the time they were received, e.g., «As of 3 minutes ago». No invented values are shown — only the last value provided by the BFF.
  • Stop reasons, order transitions (including stops), completion rate reports, maintenance calls, and defect records are placed in the queue. Inputs affecting the same order or stop maintain queue order; if a preceding item is rejected, subsequent items are not sent until human judgment is made.
  • Recovery involves polling /api/health at intervals of 2, 4, 8, 15 seconds (then repeating 15s). If both the DB and Platform are UP, it restores the connection, sends the queue, and re-reads the screen.
  • Quantity corrections, maintenance stage moves, maintenance call cancellations, and defect record cancellations are not queued — these are writes based on the value and version of that moment and are only accepted when connected. Cancellation of a maintenance call already in the queue is handled by removing it from the queue.
  • Timeouts for V5 reads are 2s for connection and 5s for response. Heavy report summaries (plan comparison, defects, maintenance) may wait up to 20s; exceeding this does not open the circuit — only that card fails via 504 platform_slow and does not transition to a disconnection state.

PlantPulse AI Chat​

The «Ask AI» feature on the tablet is proxied by the BFF to the PlantPulse AI field chat API. This is a separate connection from Platform V5, so Platform connectivity is not affected if the AI is slow or offline.

BFFAI SidePurpose
GET /api/v1/chat/capabilitiesGET /api/v1/chat/capabilitiesWhether to show the button (60s memory). Those without scope cannot ask AI and see «None».
POST /api/v1/chat/streamPOST /api/v1/chat/streamReceives response via SSE and streams it as-is.
POST /api/v1/chat/cancelPOST /api/v1/chat/cancelStop. Conversation for the same person only.
  • Address and keys are stored in app DB fl_setting under ai.url · ai.api_key (Settings → AI). Keys are sent only via X-API-Key and are not included in logs, responses, or exception messages.
  • Requests include the employee number of the «Person asking on behalf» (same value as X-PP-Performed-By) and their scope (same rule as scope check). If no scope exists, the AI is not called and a 403 is returned.
  • Screen language is sent as Accept-Language.

Authentication — Three Layers​

LayerDescriptionLocation
Platform KeyPlatform Master API Key. Held only by BFF.Container environment variable PP_MASTER_API_KEY
Device TokenRandom number issued during tablet registration. Only the hash is stored in the DB. No expiration; ends when deactivated by admin. Sent via Authorization: Bearer.Browser localStorage (fl.device_token). Not present in other browsers, incognito windows, or browsers with cleared site data, thus unregistered.
Operator SessionOpened with login number + PIN. PIN is verified by Platform (or login cache if unreachable). Session points to Platform employee ID, is valid until auto-logout time, and is tied to the device token. Sent via X-FL-Session.Tab sessionStorage — persists across refreshes, ends when tab is closed (uses memory if storage is unavailable).
  • Platform V5 does not accept USER role tokens (only ADMIN · API). Thus, individual Platform tokens cannot be given; the master key is used. The actor is recorded in both V5 records performed_by (above X-PP-Performed-By) and app audit logs (fl_audit — employee number, device, V5 path, response code, Idempotency-Key).
  • In Platform audit logs, this app's calls are distinguished by actor master-api-key and User-Agent PlantPulse-frontline-web/<version> in proxy logs.
  • All BFF handlers specify access levels — denial is the default if not specified.
  • Tokens and PINs are not included in logs, responses, or exception messages.

Role Check​

Permitted roles are specified via @Access for each handler. The server blocks access independently of hiding buttons on the screen.

ActionPermitted Roles
Maintenance Acknowledge, Start Repair, CompleteMaintenance, Administrator
Stop OrderForeman, Administrator
Call Maintenance, Cancel Maintenance Call, Record Defect, Cancel Defect RecordAll four roles. Cancellations are re-verified by the handler — must be the caller or Foreman/Administrator.
Settings (/v1/admin/)Administrator

Scope Check​

Assigned scope is checked after the role. All field requests pass through a single point scope/ScopeInterceptor (after the authentication interceptor).

  • Targets are equipment, lines, orders (verifying equipment via V5), maintenance orders, and defect records specified in the path or query. Reads must be within scope, and writes must be RW.
  • Out-of-scope requests return 403 scope_denied; attempts to write to read-only scopes return 403 scope_read_only.
  • @OpenToReadOnly (Call Maintenance, Cancel Maintenance Call) are checked at the read level — people who can only «view» equipment must still be able to call maintenance. Cancellation of others' calls is blocked by the handler.
  • Login Scope = Account Scope ∩ Device Default Scope (ScopeGuard.within). If the account node is the same as or below the device node, it is kept. If above, the device node is given as the account node's access. Non-overlapping nodes are discarded. If the account is scope_all, it is the device node RW. The device does not expand scope or upgrade RO to RW. Devices without an administrator role and default scope do not narrow it. The device default scope can be at any stage: site, area, line, or equipment (DeviceScope).
  • Nodes are inherited downwards. If a piece of equipment receives both RW and RO, RW takes precedence. Nodes resolve the parent chain via V5 and are cached for 5 minutes. Nodes deleted in V5 have zero access.
  • Dashboards and reports require the node itself to be within scope. Receiving one piece of equipment does not open that line's dashboard.
  • Lists are narrowed by the handler — scope tree, line cards/alarm counts, work orders (access bundled per row), and maintenance boards.
  • Fail-closed. If neither scope_all nor nodes exist, nothing can be read. Scope comes from the Platform account (pp.mm_app_account.scope_all · pp.mm_app_account_scope), and there is no API in the app to modify scope. V5 treats empty node lists as «Nothing», and nodes are limited to 200.
  • Administrator roles bypass scope checks. Management APIs (/v1/admin/) skip scope checks and only verify the administrator role.
  • Non-logged-in tablets («View Now without PIN») read the device's default scope only via RO.

The login response includes four scope fields.

FieldMeaning
scope{"all": true} or node list (id · kind · access)
scope_noneNothing viewable on this tablet — web shows only guidance and logout, no menus
scope_none_reasonReason when scope_none. ACCOUNT means account has no scope («Scope not assigned»), TABLET means account scope does not overlap with tablet scope («No viewable scope on this tablet»)
home_scopeThe first equipment or line to open if the starting scope (last selected, or device default) is outside the user's scope

Environment Variables​

VariableDescription
PP_TZDisplay timezone (required). Storage is UTC epoch; screen uses this timezone. Date rollover, «Today», and windows spanning midnight also use this timezone.
PP_LANGInstallation language (required). Must be a tag from the supported language list (currently ko · en), accepting case and regional variants (en-US). If not in the list, the app does not start.
PP_SERVER_URLPlatform V5 address
PP_MASTER_API_KEYPlatform Master API Key. Same value as server-web, batch-web, and warehouse-web — all four must be restarted together if changed.
PP_POSTGRES_HOST · PP_POSTGRES_PORTData Lake PostgreSQL
PP_DB_NAME · PP_PG_USER · PP_PG_PASSWORDPlatform database (pp) and account. Same name and value as server-web, batch-web, and warehouse-web. There are no frontline-specific accounts/passwords.

The container will not start if PP_MASTER_API_KEY · PP_PG_PASSWORD · PP_POSTGRES_HOST are missing. JVM heap is fixed at 512 MB (direct 128 MB). No new variables were added for assigned scope, maintenance, defects, or reports.

To add a new screen language, add web/src/i18n/<태그>.json to the web bundle and a line to the server's supported language list. The schema (V5) remains unchanged.

Fields to be Opened in Future Steps​

The following fields are not yet in the app. They have no placeholders on the screen and will be opened via BFF calls once general V5 APIs are available on the Platform.

FieldCurrent StatusRequired from Platform
Weekly Trend · Previous Period ComparisonNone (Daily trend opened via /kpi/daily)Weekly period series
Energy (EMS)None — no dashboard tab or report sectionEMS API
Line-level Loss by Stop ReasonNone — drawn only for equipmentLine scope stop loss aggregation
Hourly Performance / TargetNoneHourly target · performance API
Shift Loss WaterfallNone — shift OEE window only has OEE, availability, performance, quality, and top stop reasonsLoss decomposition
Manual Stop Registration · Quantity by Time SlotNoneStop write path, quantity by time slot write

Among the fields previously in this table, the following have been opened.

FieldImplementation Method
Actor (Employee number in Platform record)X-PP-Performed-By in all writes → V5 performed_by
Call Maintenance → Acknowledge → Start Repair → CompleteV5 /maintenance-orders
Defect Quantity + ReasonV5 /defects
Daily TrendV5 /kpi/daily
Shift · Daily · Weekly ReportBFF GET /v1/report aggregates V5 KPI, order, and summary APIs