File size: 10,333 Bytes
e42a7af
0e23a69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e42a7af
0e23a69
e42a7af
 
 
0e23a69
 
 
 
e42a7af
 
 
0e23a69
 
 
e42a7af
 
0e23a69
 
 
 
e42a7af
 
 
0e23a69
 
 
e42a7af
 
0e23a69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e42a7af
 
0e23a69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# BoardSim — Frontend API Specification

## Overview
The frontend communicates with the backend via REST/HTTP or WebSocket endpoints. The backend is a FastAPI server running at a configurable base URL (default: `http://localhost:8000` for local dev, or `https://<USER>-board-sim-env.hf.space` for production).

**Key Principle**: Frontend and backend are fully decoupled. The frontend only needs to know these endpoints; it does not import any backend code.

---

## 1. REST Endpoints

### `POST /reset`
**Purpose**: Start a new game episode.

**Request Body**:
```json
{
  "seed": 42,
  "episode_id": "optional-uuid-string"
}
```

**Response** (200 OK):
```json
{
  "observation": {
    "state": {
      "round": 1,
      "revenue": 2000000.0,
      "burn_rate": 1200000.0,
      "runway_months": 14.0,
      "product_readiness": 0.45,
      "market_share": 0.08,
      "team_morale": 0.70,
      "investor_confidence": 0.65,
      "regulatory_risk": 0.20,
      "profitability_score": 0.0,
      "trust": {
        "CTO": 0.5,
        "CFO": 0.5,
        "Investor Rep": 0.5,
        "Independent": 0.5
      },
      "trust_history": [
        {
          "round": 0,
          "CTO": 0.5,
          "CFO": 0.5,
          "Investor Rep": 0.5,
          "Independent": 0.5
        }
      ],
      "history": [],
      "done_reason": null,
      "winning_decision": null
    },
    "event": "New Competitor Entry — A larger competitor enters your core market with aggressive pricing and threatens your customer base.",
    "options": [
      "cut_prices",
      "double_down_on_quality",
      "form_strategic_partnership"
    ],
    "npc_statements": [
      {
        "role": "CTO",
        "statement": "From an operational standpoint, the trade-offs here are clear. I'm voting double_down_on_quality.",
        "vote": "double_down_on_quality",
        "confidence": 0.78
      },
      {
        "role": "CFO",
        "statement": "From a fiduciary standpoint, only one of these is defensible. I'm voting cut_prices.",
        "vote": "cut_prices",
        "confidence": 0.66
      },
      {
        "role": "Investor Rep",
        "statement": "We were not funded to play it safe. I'm voting form_strategic_partnership.",
        "vote": "form_strategic_partnership",
        "confidence": 0.70
      },
      {
        "role": "Independent",
        "statement": "Long-term reputation outlasts any single quarter. I'm voting double_down_on_quality.",
        "vote": "double_down_on_quality",
        "confidence": 0.59
      }
    ],
    "round": 1
  },
  "done": false,
  "info": {
    "episode_id": "uuid-string",
    "seed": 42
  }
}
```

---

### `POST /step`
**Purpose**: Submit the agent's decision for the current round.

**Request Body**:
```json
{
  "action": {
    "decision": "double_down_on_quality",
    "coalition_pitch": "Investing in product quality protects long-term reputation and reduces operational risk while preserving margin discipline."
  }
}
```

**Response** (200 OK):
```json
{
  "observation": {
    "state": {
      "round": 2,
      "revenue": 2000000.0,
      "burn_rate": 900000.0,
      "runway_months": 18.5,
      "product_readiness": 0.45,
      "market_share": 0.08,
      "team_morale": 0.65,
      "investor_confidence": 0.60,
      "regulatory_risk": 0.20,
      "profitability_score": 12.34,
      "trust": {
        "CTO": 0.65,
        "CFO": 0.70,
        "Investor Rep": 0.40,
        "Independent": 0.55
      },
      "trust_history": [
        {
          "round": 0,
          "CTO": 0.5,
          "CFO": 0.5,
          "Investor Rep": 0.5,
          "Independent": 0.5
        },
        {
          "round": 1,
          "CTO": 0.65,
          "CFO": 0.70,
          "Investor Rep": 0.40,
          "Independent": 0.55
        }
      ],
      "history": [
        {
          "round": 1,
          "event_title": "Round 1 — Series-B runway crunch",
          "agent_decision": "cut_costs",
          "winning_decision": "cut_costs",
          "reward": 1.25,
          "profitability_before": 0.0,
          "profitability_after": 12.34
        }
      ],
      "done_reason": null,
      "winning_decision": "cut_costs"
    },
    "event": "Round 2 — Enterprise contract w/ source-code escrow\nDescription: A Fortune 500 enterprise wants to sign a $5M contract but demands source code escrow.",
    "options": [
      "accept_deal",
      "negotiate_terms",
      "reject_deal"
    ],
    "npc_statements": [
      {
        "role": "CTO",
        "statement": "...",
        "vote": "...",
        "confidence": 0.XX
      }
    ],
    "round": 2
  },
  "reward": 1.25,
  "done": false,
  "info": {
    "round": 2,
    "winning_decision": "cut_costs",
    "winning_vote_tally": {
      "cut_costs": 4.2,
      "raise_capital": 1.3,
      "reduce_scope": 0.5
    },
    "pitch_scores": {
      "CTO": 0.0,
      "CFO": 0.0,
      "Investor Rep": 0.0,
      "Independent": 0.0
    }
  }
}
```

---

### `GET /health`
**Purpose**: Health check. Confirms backend is running.

**Response** (200 OK):
```json
{
  "status": "healthy"
}
```

---

### `GET /docs`
**Purpose**: Auto-generated Swagger/OpenAPI documentation. Use for development reference.

**Location**: `http://localhost:8000/docs` (or on HF Space at `/docs`)

---

## 2. WebSocket Streaming (Optional, Advanced)

If you want real-time streaming during training or multi-agent play:

### `WebSocket /ws`
**Purpose**: Bi-directional message streaming (not required for single-agent frontend).

Connection example:
```javascript
const ws = new WebSocket("ws://localhost:8000/ws");
ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log(message); // e.g., { "type": "step", "observation": {...} }
};
```

*(Details omitted if not used for initial frontend.)*

---

## 3. Data Models Reference

### `BoardSimObservation` (returned by `/reset` and `/step`)
```javascript
{
  state: {
    round: number,                        // 1-indexed: 1..10
    revenue: number,                      // in dollars
    burn_rate: number,                    // monthly spend in dollars
    runway_months: number,                // months until bankruptcy
    product_readiness: float (0..1),
    market_share: float (0..1),
    team_morale: float (0..1),
    investor_confidence: float (0..1),
    regulatory_risk: float (0..1),
    profitability_score: number,
    trust: {                              // per NPC, 0..1
      "CTO": 0.5,
      "CFO": 0.5,
      "Investor Rep": 0.5,
      "Independent": 0.5
    },
    trust_history: Array,                 // per-round trust snapshots
    history: Array,                       // past decisions & outcomes
    done_reason: string | null,           // e.g., "bankruptcy", "acquisition", "ipo", null
    winning_decision: string | null
  },
  event: string,                          // event title + description
  options: [string, string, string],      // 3 valid decision strings for this round
  npc_statements: [
    {
      role: "CTO" | "CFO" | "Investor Rep" | "Independent",
      statement: string,
      vote: string (one of options),
      confidence: float (0..1)
    },
    // ... one per NPC role (4 total)
  ],
  round: number
}
```

### `BoardSimAction` (sent to `/step`)
```javascript
{
  decision: string,                    // must be one of observation.options
  coalition_pitch: string | null       // optional persuasion attempt (unused in v1)
}
```

---

## 4. Error Responses

### 422 Unprocessable Entity
Invalid action format or decision not in options.

**Response**:
```json
{
  "detail": [
    {
      "loc": ["body", "action", "decision"],
      "msg": "value is not a valid enumeration member",
      "type": "type_error.enum"
    }
  ]
}
```

### 400 Bad Request
Malformed JSON or missing required fields.

---

## 5. Frontend Integration Checklist

- [ ] **Initialize**: On app load, call `POST /reset` to get initial observation.
- [ ] **Display State**: Render `observation.state` as metrics (revenue, runway, morale, trust, etc.).
- [ ] **Display Event**: Show `observation.event` (crisis title + description).
- [ ] **Display NPCs**: Render 4 NPC cards with their `statement`, `vote`, and `confidence`.
- [ ] **Render Decision Options**: Display 3 buttons (or cards) for each string in `observation.options`.
- [ ] **Handle User Click**: On decision click, POST `/step` with the selected `decision`.
- [ ] **Update UI**: Parse response observation and repeat from "Display State".
- [ ] **Terminal State**: If `done` is true, show final metrics and `done_reason` (e.g., "Bankruptcy", "IPO").
- [ ] **Optional Coalition Pitch**: Text input for `coalition_pitch` (future extension; safe to leave blank for v1).

---

## 6. Backend Base URL Configuration

For local development:
```
http://localhost:8000
```

For HF Space deployment (after `openenv push`):
```
https://<your-hf-username>-board-sim-env.hf.space
```

**Frontend environment variable** (optional):
```
REACT_APP_API_BASE_URL=http://localhost:8000
// or
REACT_APP_API_BASE_URL=https://<your-hf-username>-board-sim-env.hf.space
```

---

## 7. Example Frontend Workflow

```javascript
// 1. Reset
const resetRes = await fetch(`${API_BASE}/reset`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ seed: 42 })
});
const { observation, done, info } = await resetRes.json();

// 2. Render observation
displayState(observation.state);
displayNPCStatements(observation.npc_statements);
displayDecisionButtons(observation.options);

// 3. User clicks decision
const decision = "cut_costs"; // from button click
const stepRes = await fetch(`${API_BASE}/step`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    action: { decision, coalition_pitch: "" }
  })
});
const { observation: nextObs, reward, done: nextDone } = await stepRes.json();

// 4. Repeat or show results
if (nextDone) {
  displayEndgameScreen(nextObs.state, nextObs.state.done_reason);
} else {
  displayState(nextObs.state);
  // ... repeat
}
```

---

## 8. No Backend Imports in Frontend**OK**: `fetch("http://localhost:8000/reset")`**NOT OK**: `import { BoardSimEnvironment } from "backend"`

The frontend is a standalone web app. All communication is via HTTP/WebSocket.