Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ APP_ENV=development
LOG_LEVEL=info
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/factory_intelligence
FACTORY_STORAGE_BACKEND=jsonl
FACTORY_SOURCE_MODE=external_demo_factory
FACTORY_CONNECTOR_MODE=read_only
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
FIP_API_URL=http://localhost:8000
FACTORY_CONNECTION_PROFILES_STORE=.local/storage/connection_profiles.json
Expand Down
8 changes: 8 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ curl http://localhost:8000/health
docker compose -f infra/docker/docker-compose.yml down
```

The Workbench expects the API health endpoint to describe the active runtime
with `source_mode`, `storage_backend`, and `connector_mode`. In the Docker path,
that means external Demo-Factory/local protocol sources, Postgres-backed state,
and read-only connector behavior. Empty detection, evidence, recommendation, or
RCA/CAPA draft panels should be treated as missing runtime data until
Demo-Factory, FIP Compose, connector ingestion, and Process Sentinel have all
run.

For focused Workbench development before all Compose services land, run the API
directly from the repository root:

Expand Down
25 changes: 12 additions & 13 deletions apps/web/app/components/demo-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export function ApiConnectionBanner({
<div>
<strong>Local API connected</strong>
<span>
Workbench data is coming from the configured FastAPI target for this
simulator-backed demo.
Workbench data is coming from the configured FIP API target for the
external-source runtime.
</span>
</div>
<dl className="api-connection-details">
Expand All @@ -65,11 +65,11 @@ export function ApiConnectionBanner({
</div>
<div>
<dt>Source</dt>
<dd>
{health?.simulator_backed
? "Simulator-backed demo API"
: "Simulator-backed demo API expected"}
</dd>
<dd>{health?.source_mode ?? "External source expected"}</dd>
</div>
<div>
<dt>Connector mode</dt>
<dd>{health?.connector_mode ?? "Read-only runtime expected"}</dd>
</div>
</dl>
</section>
Expand All @@ -84,13 +84,12 @@ export function ApiErrorPanel({
<div className="state-panel error-panel" role="alert">
<strong>API connection issue</strong>
<span>
The Workbench could not reach the local simulator-backed API at{" "}
<code>{apiBaseUrl}</code>.
The Workbench could not reach the local FIP API at <code>{apiBaseUrl}</code>.
</span>
<span>
Start the demo state with <code>make demo</code>, start the API with{" "}
<code>make api</code>, then refresh this page. If the API is using a
different port, restart the Workbench with{" "}
Start Demo-Factory, then start the FIP Docker Compose stack. Check{" "}
<code>curl http://localhost:8000/health</code>, then refresh this page.
If the API is using a different port, restart the Workbench with{" "}
<code>NEXT_PUBLIC_API_BASE_URL</code> set to that target.
</span>
<span>Details: {message}</span>
Expand Down Expand Up @@ -123,7 +122,7 @@ export function LoadingState({ title = "Loading local demo data" }: LoadingState
<section className="content-panel loading-panel" aria-busy="true">
<div className="state-panel" role="status">
<strong>{title}</strong>
<span>Connecting to the local FastAPI demo backend.</span>
<span>Connecting to the local FIP API runtime.</span>
</div>
</section>
);
Expand Down
26 changes: 13 additions & 13 deletions apps/web/app/detections/[detectionId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ export default async function DetectionDetailPage({ params }: DetectionDetailPag
</Link>
<h1>Detection detail</h1>
<p className="lead">
Read-only Process Sentinel detection context from the local demo API.
Read-only Process Sentinel detection context from the local FIP API.
</p>
</div>
{!result.ok && result.notFound ? (
<MissingDataPanel
nextStep="Open the detection list and choose a current demo detection, or rerun make demo if local state was reset."
nextStep="Open the detection list and choose a current runtime detection, or check Demo-Factory and the FIP Docker Compose stack if the event store was reset."
text={
<>
The simulator-backed demo API did not return a detection for{" "}
<code>{detectionId}</code>. Open the detection list and choose a
current demo detection.
The local FIP API did not return a detection for{" "}
<code>{detectionId}</code>. Open the detection list and choose a
current runtime detection.
</>
}
title="Detection not found"
Expand Down Expand Up @@ -131,7 +131,7 @@ export default async function DetectionDetailPage({ params }: DetectionDetailPag
<h2>RCA/CAPA draft</h2>
<p>
Preview the human-reviewed RCA/CAPA draft language generated
from the selected simulator-backed detection.
from the selected external-source detection.
</p>
<Link
className="secondary-action"
Expand Down Expand Up @@ -190,10 +190,10 @@ function EvidenceTimeline({ evidenceItems }: { evidenceItems: EvidenceItem[] })
aria-labelledby="evidence-timeline-heading"
>
<div>
<span className="demo-label">Simulator-backed evidence</span>
<span className="demo-label">External-source evidence</span>
<h2 id="evidence-timeline-heading">Evidence timeline</h2>
<p>
Chronological Process Sentinel evidence from the local demo API. Use
Chronological Process Sentinel evidence from the local FIP API. Use
this to understand why the finding exists before reviewing any
recommendation.
</p>
Expand All @@ -205,11 +205,11 @@ function EvidenceTimeline({ evidenceItems }: { evidenceItems: EvidenceItem[] })
</section>
{evidenceItems.length === 0 ? (
<MissingDataPanel
nextStep="Rerun make demo to rebuild the detection and evidence timeline, then refresh this detail page."
nextStep="Start Demo-Factory, confirm the FIP Docker Compose stack is running, and check the API health endpoint before refreshing this detail page."
text={
<>
This detection exists, but the simulator-backed API did not return
evidence items for it yet.
This detection exists, but the local FIP API did not return
evidence items from the current external-source event store yet.
</>
}
title="No evidence available"
Expand Down Expand Up @@ -294,12 +294,12 @@ function buildFlagExplanation(detection: Detection): string {
detection.confidence * 100,
)}% confidence over ${formatTimeWindow(
detection,
)}. This is advisory simulator-backed context for human review, not an autonomous action.`;
)}. This is advisory external-source context for human review, not an autonomous action.`;
}

function buildTimelineMeaning(evidenceItems: EvidenceItem[]): string {
if (evidenceItems.length === 0) {
return "The detection is present, but there is no supporting timeline evidence available yet in the simulator-backed demo state.";
return "The detection is present, but there is no supporting timeline evidence available yet in the current external-source event store.";
}

const evidenceTypes = [
Expand Down
9 changes: 5 additions & 4 deletions apps/web/app/detections/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ export default async function DetectionsPage() {
<div>
<h1>Detections</h1>
<p className="lead">
Process Sentinel detections from the local demo run. Open a detection to
inspect the current summary and routing context.
Process Sentinel detections from the current external-source event
store. Open a detection to inspect the current summary and routing
context.
</p>
</div>
{!result.ok ? <ApiErrorPanel message={result.message} /> : null}
{result.ok && result.detections.length === 0 ? (
<EmptyState
nextStep="Run make demo from the repository root, start the local API, then refresh this page."
text="The local API is reachable, but it did not return any Process Sentinel detections for the current simulator-backed demo state."
nextStep="Start Demo-Factory, then start the FIP Docker Compose stack and check curl http://localhost:8000/health."
text="No Process Sentinel detections are available for the current external-source event store yet."
title="No detections returned"
/>
) : null}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "./globals.css";

export const metadata: Metadata = {
title: "Operations Workbench",
description: "Simulator-backed Factory Intelligence Platform workbench shell.",
description: "External-source Factory Intelligence Platform workbench shell.",
};

type NavItem =
Expand Down Expand Up @@ -83,7 +83,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
<section className="status-strip" aria-label="Workbench status strip">
<div>
<span className="status-label">Mode</span>
<strong>Simulator-backed demo</strong>
<strong>External-source runtime</strong>
</div>
<div>
<span className="status-label">API target</span>
Expand Down
23 changes: 14 additions & 9 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default async function OverviewPage() {
<p className="lead">
{overview.ok
? overview.context.siteDescription
: "The manufacturer demo overview needs the local FastAPI backend to show current factory context."}
: "The manufacturer demo overview needs the local FIP API to show current factory context."}
</p>
<div className="hero-actions">
<Link className="primary-action" href="/detections">
Expand All @@ -57,11 +57,15 @@ export default async function OverviewPage() {
<span className="status-value">{getApiBaseUrl()}</span>
</div>
<div className="status-row">
<span className="status-label">Demo source</span>
<span className="status-label">Source mode</span>
<span className="status-value">
{overview.ok && overview.health.simulator_backed
? "Synthetic Factory Simulator"
: "Synthetic Factory Simulator expected"}
{overview.ok ? overview.health.source_mode : "Unavailable"}
</span>
</div>
<div className="status-row">
<span className="status-label">Connector mode</span>
<span className="status-value">
{overview.ok ? overview.health.connector_mode : "Unavailable"}
</span>
</div>
<div className="status-row">
Expand Down Expand Up @@ -111,7 +115,7 @@ export default async function OverviewPage() {
<article className="metric-card">
<span className="status-label">Active detections</span>
<strong>{overview.activeDetections.length}</strong>
<p>Open Process Sentinel cases from simulator-backed data.</p>
<p>Open Process Sentinel cases from external-source event data.</p>
</article>
<article className="metric-card">
<span className="status-label">Pending recommendations</span>
Expand Down Expand Up @@ -150,8 +154,9 @@ export default async function OverviewPage() {
<>
<h2>No active detections</h2>
<p>
Run <code>make demo</code>, start the local API, and refresh this
page to populate this panel.
Start Demo-Factory, then start the FIP Docker Compose stack
and refresh this page after Process Sentinel stores runtime
detections.
</p>
</>
)}
Expand Down Expand Up @@ -251,7 +256,7 @@ function buildOverviewContext({
productName: currentBatch?.product_name ?? "Demo product unavailable",
siteDescription:
currentSite?.description ??
"Simulator-backed site context will appear when the local FastAPI backend is available.",
"External-source site context will appear when the local FIP API is available.",
siteName: currentSite?.name ?? "Factory Intelligence Platform demo",
workOrderId: importantDetection?.related_work_order_id ?? "Demo work order unavailable",
};
Expand Down
23 changes: 12 additions & 11 deletions apps/web/app/rca-capa-draft/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,19 @@ export default async function RcaCapaDraftPage({
<h1>RCA/CAPA Draft</h1>
<p className="lead">
Preview investigation-ready RCA/CAPA draft language for the selected
simulator-backed Process Sentinel detection. Draft content is human-review
required and is not automatically submitted to QMS or MES systems.
external-source Process Sentinel detection. Draft content is
human-review required and is not automatically submitted to QMS or MES
systems.
</p>
</div>
{!result.ok && result.notFound ? (
<MissingDataPanel
nextStep="Open a current demo detection and use its RCA/CAPA draft link, or rerun make demo if local state was reset."
nextStep="Open a current runtime detection and use its RCA/CAPA draft link, or check Demo-Factory and the FIP Docker Compose stack if the event store was reset."
text={
<>
The simulator-backed API did not return an RCA/CAPA draft for{" "}
<code>{result.detectionId}</code>. Open a current demo detection and
use its RCA/CAPA draft link.
The local FIP API did not return an RCA/CAPA draft for{" "}
<code>{result.detectionId}</code>. Open a current runtime detection
and use its RCA/CAPA draft link.
</>
}
title="Draft not found"
Expand All @@ -56,16 +57,16 @@ export default async function RcaCapaDraftPage({
{!result.ok && !result.notFound ? <ApiErrorPanel message={result.message} /> : null}
{result.ok && !result.draft ? (
<EmptyState
nextStep="Run make demo, then open the draft from a current detection detail page."
text="The local API is reachable, but no demo detection is available for draft preview."
nextStep="Start Demo-Factory, then start the FIP Docker Compose stack and open the draft from a current detection detail page."
text="The local API is reachable, but no external-source detection is available for draft preview yet."
title="No detection available for draft preview"
/>
) : null}
{result.ok && result.draft ? (
<div className="data-stack">
<div className="decision-note">
This RCA/CAPA draft is generated from demo detection, evidence, and
recommendation state. It is advisory decision support for human
This RCA/CAPA draft is generated from runtime detection, evidence,
and recommendation state. It is advisory decision support for human
review only; it is not a validated production record and it does not
submit anything to QMS or MES.
</div>
Expand Down Expand Up @@ -100,7 +101,7 @@ export default async function RcaCapaDraftPage({
<div>
<dt>Source</dt>
<dd>
<StatusBadge tone="info" value="Simulator-backed demo state" />
<StatusBadge tone="info" value="External-source event store" />
</dd>
</div>
</dl>
Expand Down
16 changes: 9 additions & 7 deletions apps/web/app/recommendations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,27 @@ export default async function RecommendationsPage({

return (
<section className="content-panel">
<span className="demo-label">Simulator-backed governed recommendation</span>
<span className="demo-label">External-source governed recommendation</span>
<div>
<h1>Recommendation review</h1>
<p className="lead">
Review the advisory Process Sentinel recommendation and record a human
decision. The demo does not execute industrial writeback.
decision. This recommendation is advisory and human-reviewed; no
industrial writeback is performed.
</p>
</div>
{!result.ok ? <ApiErrorPanel message={result.message} /> : null}
{result.ok && result.recommendations.length === 0 ? (
<EmptyState
nextStep="Run make demo so Process Sentinel can create the demo recommendation, then refresh this page."
text="The local API is reachable, but it did not return any governed recommendations for the current simulator-backed demo state."
nextStep="Start Demo-Factory, then start the FIP Docker Compose stack and confirm the API health endpoint is reachable."
text="The local API is reachable, but it did not return any governed recommendations for the current external-source event store yet."
title="No recommendations returned"
/>
) : null}
{result.ok && result.recommendations.length > 0 && !result.selected ? (
<EmptyState
nextStep="Return to the detection list and choose a detection from the current demo run."
text={`No recommendation is linked to detection ${detectionId}. Return to the detection list and choose a current demo detection.`}
nextStep="Return to the detection list and choose a detection from the current runtime event store."
text={`No recommendation is linked to detection ${detectionId}. Return to the detection list and choose a current runtime detection.`}
title="No linked recommendation found"
/>
) : null}
Expand All @@ -53,7 +54,8 @@ export default async function RecommendationsPage({
<div className="decision-note">
Recommendations are advisory decision support. A named human reviewer
must approve, reject, or defer before any high-impact action is
considered, and this demo records only local simulator state.
considered. The Workbench does not perform industrial writeback,
product disposition, QMS/MES updates, or production CAPA creation.
</div>
<RecommendationReviewPanel initialRecommendation={result.selected} />
<section className="workflow-links" aria-label="Recommendation workflow links">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function RecommendationReviewPanel({
>
<p className="form-help" id="decision-form-help">
Enter a reviewer and decision reason before approving, rejecting, or
deferring this simulator-backed recommendation.
deferring this external-source recommendation.
</p>
<div>
<label htmlFor="reviewer">Reviewer name</label>
Expand Down
Loading
Loading