technext-edge · internal

Team Guide

Full handoff for anyone touching this repo: what it is, how to run it, how to test it, how to deploy it, and the gotchas that already cost an afternoon once.

1What this is

This is the Edge layer from the Core & Edge Blueprint: Odoo owns prices, folios, invoices; this repo is everything outside that. Today it holds exactly one working piece — the AI Extractor (packages/extractor/) behind a minimal BFF (apps/casa-bff/), exposed as POST /v1/extract.

The Draft store, the Contract pod's generated Odoo client, and the rest of the BFF endpoints from the Playbook do not exist here yet. See docs/adr/ for the decisions this build is actually based on — the Blueprint and Playbook sites hold the reasoning behind them.

2Prerequisites

  • Node ≥ 18 (developed against Node 22)
  • The Vercel CLI (npm i -g vercel) — keep it current; an old CLI talking to a newer backend silently breaks vercel inspect/vercel logs (see §8).
  • Access to the aidev1-technexts-projects Vercel scope, or your own scope for a separate deployment.
  • A Gemini API key from Google AI Studio for local runs.
Free tier caps at 20 requests/day and a tight per-minute burst limit. Fine for a handful of manual tests, not enough for a real eval run — see ADR-005a.

3Running it locally

git clone https://github.com/TechNextSG/technext-edge.git
cd technext-edge
npm install
npm test                     # extractor unit tests — no API key needed, no network

To actually call a model:

cp .env.example .env.local   # fill in GEMINI_API_KEY
npm run dev:bff              # http://localhost:8787

Open http://localhost:8787/ for the test console (see §4), or curl it:

curl -s localhost:8787/v1/extract -H 'content-type: application/json' \
  -d '{"text":"4 of us, next Saturday, 3 nights"}'

4The test console

A plain page at GET / — not the real estimator UI (that belongs to the Edge UI pod) — for trying extraction without curl or Postman. Live at technext-edge-casa-bff.vercel.app. Visual identity matches apps/estimate-tool in casa-escondida-tools on purpose.

It's behind Vercel's Deployment Protection (SSO wall) — anyone hitting the bare URL without a session sees a login page, not the app. To get in, open this once per browser (sets a cookie):

https://technext-edge-casa-bff.vercel.app/?x-vercel-protection-bypass=<secret>&x-vercel-set-bypass-cookie=true
Ask in the team channel for the current bypass secret. It's an infra token, not a user credential, but still don't paste it somewhere public — rotate it if it ever looks exposed.

The provider override panel on the page lets you paste your own key and pick a different provider (Gemini / DeepSeek Flash / DeepSeek Pro) for one request, without touching any Vercel config. The key goes straight from your browser to this server and nowhere else — session storage only, cleared when the tab closes.

5Deploying

Always run Vercel commands from the repo root, never from apps/casa-bff/. The deploy entry point is api/index.ts at the root — it imports packages/extractor by relative path, so a deploy triggered from inside apps/casa-bff/ uploads only that subtree and 404s on the extractor package during install.

vercel link                       # first time only
vercel env add GEMINI_API_KEY production
vercel env add GEMINI_API_KEY preview

rm -rf .vercel/output
vercel build --yes --target production
vercel deploy --prebuilt --prod --yes

Building locally first (vercel build then --prebuilt) is the reliable path — a plain vercel deploy --prod works too but gives you nothing to inspect if something goes wrong mid-build. If a deploy sits at "Building…" for what feels like too long, check vercel logs <deployment-url> first — a 429/quota error, a bad commit-author check, and a genuine hang all look identical from the outside.

6Environment variables

VariableWhereRequiredNotes
GEMINI_API_KEYProd + Previewif provider is gemini (default)Free tier isn't enough for a real eval run — see ADR-005a.
GEMINI_MODELoptionalnoDefaults to gemini-2.5-flash. Verify against ai.google.dev before changing.
EXTRACTOR_PROVIDERoptionalnogemini (default) · deepseek-flash · deepseek-pro
DEEPSEEK_GATEWAY_KEYoptionalif provider is a DeepSeek valuePersonal LiteLLM gateway key — ask Anthony. $12 budget, shared across everything you use it for.
DEBUG_EXTRACToptional, dev onlyno1 includes zod issues / stack traces in error responses. Remove after debugging.

You don't need to touch env vars to try a different provider for one request — see the per-request override in §4 and §7.

7Eval harness

packages/extractor/eval/ scores against the Playbook's five thresholds (fabrication, required-field accuracy, verbatim evidence, latency; question-targeting needs a human pass, not wired up). Two rules that matter most:

  • dataset.synthetic.json decides nothing. Researched but made up. Real decisions wait for dataset.real.json — Eloa's 30 real, name-masked messages.
  • Every result gets read against the actual thresholds, not vibes. A fabricated field is an automatic disqualifier regardless of how good everything else looks.
export EVAL_BYPASS_SECRET=<bypass secret from §4>
node packages/extractor/eval/runner.mjs                    # server's default provider

# test a specific provider without touching Vercel config:
export EVAL_PROVIDER_API_KEY=<your key for that provider>
node packages/extractor/eval/runner.mjs --provider deepseek-flash

PowerShell: $env:EVAL_BYPASS_SECRET="..." instead of export.

8Known gotchas read before debugging

vercel.json needs "framework": null. Without it, Vercel's Hono auto-detection expects an entrypoint at the repo root or src/ and fails with "No entrypoint found" — conflicts with the api/ + rewrites layout used here.
Root package.json needs "type": "module". Without it, the deployed function's ESM import syntax gets loaded as CommonJS and crashes — even on GET /healthz, which doesn't touch the extractor at all.
Vercel's function bundler doesn't reliably resolve the npm-workspace symlink at runtime. @technext-edge/extractor typechecks and builds locally, then 404s in the deployed Lambda with ERR_MODULE_NOT_FOUND. app.ts imports the package by relative path instead.
The git commit's author matters, even for a plain vercel deploy. Vercel checks commit-author permissions against the project owner on Hobby plans; a mismatched local git identity gets silently stuck ("Deployment Blocked"), which looks identical to a hung build from the outside.
A transport failure and a validation failure are different problems. extract() throws different error types for each; app.ts maps them to different HTTP statuses (429 vs 422). Collapsing them back into one generic error is what made a Gemini quota error read as "the model can't parse this message" during the eval dry run.

9Before this becomes the real deliverable

  1. Swap packages/extractor/src/schema.ts for the type generated from Phillip's frozen contracts/casa/estimate-api.v1.yaml — it's a placeholder guess right now, flagged inline.
  2. Confirm the real 4 house-norm fields and their default values with Jett/Eloa — houseNorms.ts is guessed.
  3. Get billing enabled on the Gemini API key — the free tier cannot run a 30-message × multiple-provider bake-off at all.
  4. Once Eloa's 30 real messages exist, drop them into packages/extractor/eval/dataset.real.json and run the same harness — the result becomes ADR-005b.