בס״ד

MIS — FINALSTATEENGINE Architecture

docs/MIS_FSE_ARCHITECTURE.md · last changed (pre-VM history) · rendered from GitHub master

MIS — FINAL_STATE_ENGINE Architecture

Status: Canonical architecture doc. Supersedes docs/MIS_REVIEW_2026-04-27.md (which becomes the Sam-feedback bench feeding into this doc) and docs/MIS_PHASE2_BLUEPRINT.md (which described an earlier Phase 2 implementation attempt — see §11 for relationship).
Author: synthesized from Sam's Apr 27 mobile-Claude handoff + Drive verification + cross-reference against existing workspace docs.
Captured: 2026-04-27 11:30 PM EDT · Session 17 · Claude Code Opus 4.7 (1M).


1. The one-line summary

The MIS email system has data but not decisions, signals but not authority. The fix is one tab — FINAL_STATE_ENGINE — that every surface reads from. No surface (email, execution_playbook, Architect, SMS) ever makes its own classification call again.


2. The four sheets — verified

Drive metadata confirmed all 4 IDs + ownership + recent activity 2026-04-27.

Sheet 1 — MIS Production (current, partly broken brain)

Field Value
Drive ID 1cosuFrU_EJRAprVMm-FEpmddSUTVQWhlI0tHpS1w2S4
Title MIS_v7.1_gsheet
Owner sam@hookstreetcapital.com
Container-bound script emailDailySnapshot · ID 1r9vWL1DsqSloDL8OteFNekkDZihFw_5jtTBSJ0UjbEigwRxGlILtUzbI
GitHub mirror zee78900/MIS (clasp-pushed from MIS/src/)
Code version MIS MASTER v11.0f (after Session 17 commits 465fb6b, 6e49390, f4eddbd, b145fc6)
Sends Morning Brief (~9:53 AM), Trade Action / Playbook (~2:05 PM), Power Hour (~3:00 PM) — Mon-Fri only. Sunday Week-Ahead (~6:05 PM).
What works LIVE_GUARD · REPORT_SNAPSHOTS · HOLDINGS_CLEAN · NEWS_CATALYST · CONTROL · RUN_LOG · DIAGNOSTICS · history tabs · Schwab OAuth plumbing
What's broken See §4 root-cause table

Sheet 2 — March 5 Backup (formula reference — DO NOT BUILD FROM IT)

Field Value
Drive ID 17SRUho-vIQ1J2Rb_xh6Uk7-PUD1orJeQIr64ctSSeEw
Title Copy of MIS_v7.1_gsheet - March 5, 3:01 PM
Modified 2026-04-28 01:29 UTC (~9:29 PM EDT) — actively used by Sam as live formula reference
Purpose Formula reference only. Compare formulas, not values. Restore formula structure into Sheet 1.
Confirmed has "Proxy ETF" anchor present in Daily_Snapshot · Flow Strength Index varying per-ticker · full sector table with breadth · Reference_Rules with full scoring weights · Snapshot tab 57 columns (ATR14, Stop 1×/1.3×/2×, Risk%, Shares, Gate OK/REJECT) · execution_playbook with decision fields (Entry Signal, Stop, Risk%, Target%, R:R, Shares, SACS, VSM, Flow, Trend, IPQ, Action Verdict, Reason, Action Notes)

Sheet 3 — Architect V11 (Microservices Edition) — sandbox only

Field Value
Drive ID 1MU_NiSnoVy_SSf3Cldli9YfuQ-cckcntjlcCqefNKRw
Title Architect V11 (Microservices Edition)
Created 2026-04-15 — same day docs/MIS_PHASE2_BLUEPRINT.md was drafted
Modified 2026-04-27 19:18 UTC (~3:18 PM EDT today)
Triggers Running its own Power Hour email (subject: ⚡ MIS Power Hour | <date>)
Has Working sector table with breadth (14 sectors) · strict candidate gate (2.5% risk threshold) · Quiet Alpha section · "No setups passed" fires correctly
Confirmed bug Apr 27 said "No setups today / INTC blocked weak setup" while Sheet 1 simultaneously said "5 Lean In including INTC." Unreconciled conflict — exactly the failure FSE is built to prevent.
Role going forward Sandbox only. No new features. No expanded triggers. Must become subordinate to FSE once built.

Sheet 4 — zGoogle Finance Investment Tracker (separate Schwab personal trading)

Field Value
Drive ID 1QUIJQBLTSkGcfbQ_nd1T7wvkOqv0fvni72lLREj18H8
Title zGoogle Finance Investment Tracker (NOT "Z Investment Tracker / Entry Pad" — mobile-Claude doc had wrong title)
Modified 2026-02-26 (over 2 months ago — not actively maintained)
Account scope Schwab (different from MIS, which tracks Fidelity Invest n Save Z29720600 + Joint Brok Z29835692)
Tabs Tracker (personal obligations + cards) · Positions (lot-by-lot trade history with per-lot stop/target/R:R + wash-sale awareness — NVDA 8 lots Nov-Dec 2024, TSLA, JPM) · Entry Pad (per-ticker research form: ATR stop, target, R:R 3.0, 13W/52W proximity, volume vs avg, volatility, 40+ day daily close history, momentum flags, risk box) · Watchlist · Copy of Entry
Has formula errors Yes (per Sam's mobile session)
Convention Blue font cells = Mildred input fields (preserve)
Role going forward Phase 5+ rebuild — clean formula errors, standardize stop/target, add FSE lookup, become per-trade decision surface. Schwab and Fidelity not yet reconciled.

Other MIS-related Drive files (per Drive search 'MIS' on 2026-04-27): MIS v8 (1TI6Pss, March 2026 stale), MIS v7.3 (1Xy9ZoLR, March 2026 stale), MIS_v7.1_gsheet.xlsx (Excel), 6 historical Investment_Tracker variants (mostly 2025). All read-only legacy.


3. The architecture decision (final, agreed)

One rule

FINAL_STATE_ENGINE is the single source of decision truth. No surface independently classifies tickers. All surfaces read from FSE only.

The three states per ticker

Every ticker resolves to exactly three states:

State Question it answers Examples
ScannerState What does the engine see technically? Compression/Uptrend, Breakout Watch, Momentum, Pullback, Downtrend, Reversal Watch
RiskState What do the gates say? Clear, Blocked, Rejected, Conditional
FinalState What is the user allowed to do? ADD, STARTER, WATCH, CONDITIONAL, BLOCKED, REJECTED, REDUCE, EXIT, IGNORE

Resolver order (first gate that fires wins)

  1. Formula error or stale data → REJECTED / DATA_STALE
  2. Live Guard BLOCK → BLOCKED (stripped from all sections, shown once)
  3. Existing position at loss threshold → REDUCE or EXIT
  4. Portfolio concentration violation → REJECTED / PORTFOLIO_OVERLAP
  5. ATR stop distance > max allowed → REJECTED / SETUP_DISTANCE_FAIL
  6. Dollar risk > max portfolio risk → REJECTED / PORTFOLIO_RISK_FAIL
  7. Grade below B → WATCH (unless named exception fires with all conditions)
  8. SACS < 60 → cannot be Lean In. SACS 40-59 = STARTER only if all gates pass.
  9. Extended move > 1.5× ATR intraday → CONDITIONAL / EXTENDED
  10. No defined stop or trigger → WATCH
  11. Passes all gates → STARTER or ADD (ADD only if live trigger confirmed)

Reject codes (canonical list)

DATA_STALE · LIVE_GUARD_BLOCK · NEWS_BLOCK · SHOCK_DOWN · EARNINGS_RISK · RISK_FAIL (two flavors: SETUP_DISTANCE_FAIL, PORTFOLIO_RISK_FAIL) · GRADE_FAIL · SACS_FAIL · FLOW_FAIL · SECTOR_FAIL · NO_TRIGGER · EXTENDED · PORTFOLIO_OVERLAP · MISSING_STOP · MISSING_TARGET · MISSING_SIZE · FORMULA_ERROR

Trade Permission Tiers (email top line — NOT binary Yes/No)

  1. No new trades
  2. Watch only
  3. Starter only (delayed data, limit orders)
  4. Selective adds allowed
  5. Reduce risk
  6. Exit mode

ADD vs STARTER without live triggers


4. Confirmed root causes of current failures (Sheet 1)

Component March 5 (healthy, Sheet 2) Current (broken, Sheet 1) Root cause
Flow Strength Index 75-91, varies 76.00 for ALL tickers Column AC formula simplified, lost Rotating state + sector z-score
SACS composite 40-55 range 28-42 range Reference_Rules weights gutted, composite simplified
Snapshot tab 57 cols, full gate 14 cols, no gate Downgraded in a rebuild session
Reference_Rules Full scoring weights Basic VIX thresholds only Rebuild stripped scoring layer
execution_playbook Decision fields (Entry Signal, Stop, Risk%, Target%, R:R, Shares, Action Verdict) Bracket orders Replaced with execution table
Sector table Working, 16 sectors "No sector rows found" "Proxy ETF" anchor label deleted/renamed
Lean In threshold SACS ≥ 60 actionFlag only Threshold removed
High Conviction gate Risk% ≤ 2.5% No gate Never ported from March 5
Leadership/Pressure Working "Leadership: ." Sector empty, null not suppressed
AMD signal Breakout Watch Lean In (wrong) Flow=76 default fires action flag
INTC classification REJECT (risk 3.14%) High Conviction No risk gate, no SACS threshold

The Apr 26-27 v11.0e/f fixes (misWireEarningsToTickers_, misRebuildSectorMapFormulas_, misPopulateCatalystFromNews_, NONE-skip, sentiment self-doc, ME-seed) addressed the empty-Catalyst / empty-Sector / earnings-junk surface symptoms but did NOT touch the underlying Flow / SACS / Snapshot regression that this table identifies. Those are the Session 2 (Brain Restore) targets.


5. New tabs to build

FINAL_STATE_ENGINE (30 columns)

One row per ticker per run.

AsOf · RunType · Ticker · Price · ChangePct · ScannerState · SetupType · SACS · Grade · Flow · Trend ·
RiskPct · PortfolioRiskDollar · PortfolioRiskPct · Sector · SectorBreadth · LiveGuardState · NewsState ·
EarningsState · PortfolioOverlapState · ExtensionState · FinalState · PrimaryRejectCode ·
SecondaryRejectCode · ActionAllowed · Stop · Target · RR · Shares · Reason · DataQuality

FINAL_STATE_HISTORY

Same columns as FSE. Append-only log. Powers the Morning Brief vs Power Hour delta block — read from this, never from email body text.

SURFACE_CONFLICT_CHECK

Columns: AsOf · Ticker · Surface · Surface_Action · FSE_FinalState · ConflictFlag · ConflictReason
Actively monitors disagreement.
- If execution_playbook = BUY LMT but FSE.FinalState ≠ ADD/STARTER → log CONFLICT, disable row.
- If email candidate not in FSE approved list → log CONFLICT, suppress.
- If Architect blocked but FSE = ADD → log CONFLICT, DataHealth = RED.


6. Required named ranges

No hardcoded column positions anywhere. Hardcoded columns are how this system keeps getting brittle between sessions.

FSE

FSE_Ticker · FSE_FinalState · FSE_PrimaryRejectCode · FSE_ActionAllowed · FSE_Stop · FSE_Target · FSE_Shares · FSE_DataQuality · FSE_AsOf · FSE_RunType

Control / Rules

RULES_VIX_Normal · RULES_Risk_Per_Trade · RULES_Max_Setup_Distance · RULES_Max_Portfolio_Risk · SNAP_Stop_1p3_ATR · SNAP_RiskPct_1p3_ATR · SNAP_TradeGate


7. Diagnostics as blocking gates

Current diagnostics (already confirmed in Sheet 1)

New rule: diagnostics feed the resolver

Data Health Score: GREEN / YELLOW / RED

RED kills ADD/STARTER.

NEW: Flow Clustering Diagnostic

If more than 35% of active tickers have identical Flow Strength → raise FLOW_CLUSTER_WARN.

This single diagnostic would have caught the 76.00-for-all-tickers problem the day it appeared instead of weeks later.


8. The 9-session build plan

Anti-80% rule (non-negotiable): do not proceed to the next session until the current session's acceptance test passes. One ticker end-to-end before scaling. Session Stop Rule (any of) triggers a hard stop:
1. Current acceptance test passes — stop clean
2. Blocker discovered and documented — stop and surface it
3. Production safety uncertain — stop
4. Formula/schema mismatch requires human review — stop
5. Agent tempted to broaden scope — stop

Session 1 — FSE Foundation (foundation only — no logic restoration)

Scope: 7 steps, nothing else.
1. Clone / freeze current MIS — make a copy of production sheet first; name it MIS_v7.1_gsheet — CLONE 2026-MM-DD. Do not repair live production first.
2. Create FINAL_STATE_ENGINE tab — 30 columns above, frozen header row, no formulas yet.
3. Create all named ranges — FSE_ + RULES_ + SNAP_ per §6.
4.
Create FINAL_STATE_HISTORY tab — same schema as FSE, append-only, frozen header.
5.
Create SURFACE_CONFLICT_CHECK tab — schema per §5, frozen header, no logic yet.
6.
Before writing any code, produce the expected-state table* — put it in a doc-block at the top of any new function:

Ticker Expected ScannerState Expected RiskState Expected FinalState Why
INTC Compression/Uptrend Risk fail (3.14% stop) WATCH or REJECTED Conflicting surfaces Apr 27
MRVL Breakout Watch Live Guard BLOCK BLOCKED NEG_NEWS / TICKER_BLOCK
JBLU Downtrend SHOCK_DOWN BLOCKED -6.65% shock
NVDL Momentum Extended (+8% move) CONDITIONAL Do not chase
AMD Compression/Uptrend Flow signal check WATCH (not Lean In) Was mislabeled
BLK Compression/Uptrend Risk pass (1.56%) STARTER candidate Low risk example
SCHG Compression/Uptrend Risk pass (2.17%) STARTER candidate ETF/low vol
SOXX ETF/Semis Sector weak WATCH or REJECTED Semis -1.02%
  1. Wire INTC end-to-end manually — read INTC from Momentum_Engine + LIVE_GUARD, apply resolver logic (hardcoded for now), write one row into FINAL_STATE_ENGINE. Verify FinalState = WATCH or REJECTED. If this fails → stop, document blocker, do not proceed.
  2. Prove surfaces should read from FSE — in emailDailySnapshot.gs, add one function: misReadFSEForTicker_(ticker) that reads from FSE by named range and returns FinalState, Stop, Target, RejectCode. Do not rebuild the email — just prove the read works for INTC. Log to RUN_LOG.

Session 1 DO NOT: restore Flow formula · restore SACS · rebuild email · touch Twilio/SMS · restore Snapshot tab · add portfolio concentration logic · build delta block.

Session 1 DONE WHEN: INTC resolves through FSE; FinalState is WATCH or REJECTED; misReadFSEForTicker_("INTC") returns correct values; named ranges all created; HISTORY + SURFACE_CONFLICT_CHECK tabs exist with correct schema; RUN_LOG shows successful test write.

Session 2 — Brain Restore (only after Session 1 passes)

  1. Diff column AC (Flow) formula between March 5 (Sheet 2) and current (Sheet 1) → restore.
  2. Diff column AZ (SACS) formula → restore.
  3. Restore Reference_Rules scoring weights from March 5.
  4. Restore Snapshot tab missing columns (target: 57 columns).
  5. Restore execution_playbook decision fields.
  6. Restore sector table "Proxy ETF" anchor.

Session 2 acceptance: Flow distributed (no value > 35% of tickers); SACS top names 45-60; Reference_Rules has Lean In + Reversal + trend + flow weights; Snapshot has ATR14 + Stop 1×/1.3×/2× + Risk% + Shares + Gate; execution_playbook has decision verdict columns; sector table renders Energy + Financials + Semis with breadth.

Sessions 3-9 (sequenced, do NOT start until prior passes)

# Scope
3 Wire all tickers into FSE. Test basket passes per §10.
4 Diagnostics as blocking gates. Data Health score active.
5 Email reads FSE only. execution_playbook gate. SURFACE_CONFLICT_CHECK logic active.
6 FINAL_STATE_HISTORY + delta block (Morning vs Power Hour from history table).
7 Email rebuild — last mile, not first.
8 SMS / Twilio — only after FSE is stable.
9 Entry Pad (Sheet 4) rebuild + bridge to FSE.

9. SMS / Twilio architecture (Session 8 — DO NOT BUILD BEFORE)

Architecture: Sam texts Twilio number → Twilio POST to Apps Script doPost() → script parses → reads FSE → reads Schwab API (with GOOGLEFINANCE fallback) → builds short SMS reply → Twilio sends back.

Commands:
- INTC → full snapshot
- INTC entry → entry checklist
- INTC block → write to Live Guard
- brief → morning summary
- holdings → P&L snapshot
- menu → command list

Schwab integration: try live price first, fall back to GOOGLEFINANCE labeled as delayed. Response tells source: META $678.42 (live) vs META $676.10 (15-min delay).

Future: proactive alerts (script texts Sam when PYPL drops through stop, INTC breaks out).

Spam prevention: save Twilio number as a contact. Personal use (texting yourself) avoids A2P 10DLC.

SMS before FSE = another surface making its own interpretation. Hard rule: do not start Session 8 until §8 Sessions 1-7 acceptance tests all pass.


10. The 15-point acceptance test (definition of done)

System is not decision-grade until ALL 15 pass. Not 13. Not 14. ALL 15.

  1. Flow is distributed, not clustered. No FLOW_CLUSTER_WARN fires.
  2. SACS uses restored weighted logic from Reference_Rules.
  3. Reference_Rules contains full scoring controls (all signal weights).
  4. Snapshot has full risk/gate/regime fields (target: 57 columns).
  5. execution_playbook cannot show BUY LMT for rejected names.
  6. Live Guard blocked names appear once only in all outputs.
  7. Grade C cannot appear under High Conviction anywhere.
  8. Diagnostics can block ADD/STARTER states.
  9. HOLDINGS_CLEAN has sector, allocation%, and theme mapping.
  10. FINAL_STATE_ENGINE outputs one row per ticker per run.
  11. Email reads FinalState from FSE only. No independent signal decisions.
  12. Delta block comes from FINAL_STATE_HISTORY, not email text blobs.
  13. Email answers 10-second questions: Can I trade? What passed? What failed? What's blocked? What risk?
  14. No live-trigger gap is hidden. ADD requires live price. STARTER = delayed + warning.
  15. Test basket resolves correctly: INTC · AMD · NVDL · MRVL · JBLU · SOXX · BLK · SCHG.

11. Relationship to existing workspace docs

Doc Status under this architecture
docs/MIS_PHASE2_BLUEPRINT.md (Apr 15) Superseded. Phase 2 prescribed a Yahoo-Finance + JS-only rewrite (Microservices). Architect V11 (Sheet 3) was the implementation attempt — it diverged and now creates the surface conflicts FSE is built to solve. Keep as historical context only.
docs/MIS_AUDIT_2026-04-26.md Still useful — bug list (sector empty, VIX trajectory frozen, duplicate triggers) feeds into Session 2 Brain Restore + Session 4 Diagnostics-as-Gates.
docs/MIS_REVIEW_2026-04-27.md Still useful — surfaces format/rendering bugs (Outlook iOS, AM/PM slot, color, direction arrows) that become fixes in Sessions 5/7 (Email rebuild).
MIS/docs/MIS_v7.1_Production_Blueprint.md Original 13-tab formula-only spec (Oct 2025). The "purely formula-driven" approach has been overtaken by Apps Script orchestration. Keep as reference.
MIS/docs/MIS v7.1 System Hardening.docx / v7.6 Script Audit.docx Binary docs — not yet parsed. Useful if Session 2 needs deeper March 5 formula context.
MIS/README.md Updated this session to reference this FSE doc.

12. Architecture doctrine (non-negotiable)


13. Secrets & sensitive identifiers — DO NOT COMMIT VALUES

The mobile-Claude handoff doc included Schwab Client ID + Schwab Account Hash + Finnhub API Key in plaintext. Per feedback_privacy_guardrails.md (stop-and-ask trigger: "paste credentials anywhere"), this doc references them by placeholder only. Actual values live in:

Secret Where it lives (DO NOT COPY HERE)
EARNINGS_API_KEY (Finnhub) MIS_v7.1_gsheetCONTROL tab → EARNINGS_API_KEY row · also retrievable via getControlValue_('EARNINGS_API_KEY') in Code.js
SCHWAB_CLIENT_ID MIS_v7.1_gsheetCONTROL tab → SCHWAB_CLIENT_ID row
SCHWAB_CLIENT_SECRET MIS_v7.1_gsheetCONTROL tab → SCHWAB_CLIENT_SECRET row
SCHWAB_REFRESH_TOKEN MIS_v7.1_gsheetCONTROL tab → SCHWAB_REFRESH_TOKEN row
SCHWAB_ACCOUNT_HASH MIS_v7.1_gsheetCONTROL tab → SCHWAB_ACCOUNT_HASH row

Per Phase 2 Blueprint §5.2: secrets should be in PropertiesService.getScriptProperties() rather than CONTROL tab cells. Migration to PropertiesService is queued for the same session that wires Schwab live-data into FSE (Session ~5-6). Until then, CONTROL tab is the source of truth and the values are NOT to be copied into any committed file, output, or chat artifact.

Card / account mapping (per reference_card_account_map.md): card 9405 = HSC business debit · card 0405 = personal checking on account 5609.


14. Open non-MIS items (do not let these get lost)


15. Anti-80% rule + the Session Stop Rule

Sam's self-identified pattern: builds to ~80% then context-switches. All previous MIS versions reflect this. Command Center web app (40%). Multiple BOS rebuilds. Architect V11 itself.

The anti-80% rule for this build:
- Do NOT proceed to the next module until the current acceptance test passes.
- One ticker end-to-end before scaling.
- Session Stop Rule enforced.
- No "almost done."

The Session Stop Rule — stop when ANY of:
1. Current acceptance test passes — stop clean.
2. Blocker discovered and documented — stop and surface it.
3. Production safety uncertain — stop.
4. Formula / schema mismatch requires human review — stop.
5. Agent tempted to broaden scope — stop.


16. The next concrete action

Paste Session 1 brief (§8 Session 1) into a Claude Code session with this doc attached. Nothing else first.

The brief is intentionally tight: 7 steps, 1 ticker (INTC), zero rebuilds. The whole point is to prove the FSE wiring works before touching anything that's already partially functional.


Generated 2026-04-27 11:30 PM EDT · Session 17 · Claude Code Opus 4.7 (1M) · synthesizing Sam's mobile-Claude handoff with Drive verification + workspace doc cross-reference

Source trail · docs/MIS_FSE_ARCHITECTURE.md @ master · rendered 2026-07-02 7:23 PM EDT by scripts/build-docs.py · the .md in the repo is the truth; this page is the phone-readable view