LethaboScofield's picture
Fix Space on ZeroGPU: register @spaces.GPU on run() (guarded; CPU compute)
6158ab4 verified
Raw
History Blame Contribute Delete
7.63 kB
"""FinIR-Intent -- minimal Hugging Face Space.
Flow: natural-language instruction -> FinIR-Intent baseline -> canonical envelope
(validated against the core `finir` package's JSON Schema) -> real FinIR runtime
execution -> result. This file contains no financial computation and no execution
logic of its own -- it only calls `finir.intent` / `finir.FinancialModel`.
python space/app.py
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
os.environ.setdefault("HF_HUB_OFFLINE", "1")
import gradio as gr
from finir import __version__ as FINIR_RUNTIME_VERSION
from finir.intent import (
SCHEMA_VERSION,
FinIRIntent,
IntentValidationError,
execute_intent,
json_schema,
)
try:
# Normal case once `finir-intent` is pip-installed (e.g. from requirements.txt
# in a real Space, or `pip install -e .` locally).
from finir_intent import build_reference_model, compile_intent
from finir_intent._version import FINIR_RUNTIME_COMPATIBLE
from finir_intent._version import __version__ as FINIR_INTENT_VERSION
except ImportError:
# Fallback for running straight out of this repo layout before the package is
# published -- see README.md "Packaging note". Only touches sys.path when the
# normal import actually fails, so an installed package is never shadowed.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
from finir_intent import build_reference_model, compile_intent
from finir_intent._version import FINIR_RUNTIME_COMPATIBLE
from finir_intent._version import __version__ as FINIR_INTENT_VERSION
# Hugging Face ZeroGPU (this Space's free hardware tier) requires at least one
# `@spaces.GPU`-decorated function to be registered at startup. This demo's compute is
# CPU-only (finir's default NumPy backend) and `run()` takes/returns plain strings, so
# wrapping it is safe and cheap. The `spaces` package only exists on ZeroGPU Spaces, so
# the import is guarded; locally (and on CPU hardware) `_gpu` is a transparent no-op and
# no GPU is ever requested.
try:
from spaces import GPU as _gpu # type: ignore[import-not-found]
except Exception: # not on a ZeroGPU Space (local dev / CI / CPU hardware)
def _gpu(fn=None, **_):
return fn if callable(fn) else (lambda f: f)
_SCHEMA_VALIDATOR = None # lazily built (jsonschema is optional at Space runtime)
def _schema_errors(envelope: dict) -> list[str]:
global _SCHEMA_VALIDATOR
try:
import jsonschema
except ImportError:
return []
if _SCHEMA_VALIDATOR is None:
_SCHEMA_VALIDATOR = jsonschema.Draft202012Validator(json_schema())
return [e.message for e in _SCHEMA_VALIDATOR.iter_errors(envelope)]
_MODEL = build_reference_model()
_MODEL.evaluate() # warm the runtime's incremental cache; result is not used here
_VERSION_BANNER = (
f"**FinIR-Intent** `{FINIR_INTENT_VERSION}`  |  "
f"**Intent schema** `{SCHEMA_VERSION}`  |  "
f"**Compatible FinIR runtime** `{FINIR_RUNTIME_COMPATIBLE}`  |  "
f"**Installed finir** `{FINIR_RUNTIME_VERSION}`"
)
_REFERENCE_MODEL_NOTE = """
Reference model (fixture data, not a real company): revenue, cogs, opex,
payment_terms, accounts_payable, inventory, capex, debt, interest_rate, cash, price,
volume -- all ZAR except where noted. See `src/finir_intent/reference_model.py`.
"""
@_gpu
def run(text: str) -> tuple[str, str, str]:
if not text or not text.strip():
return "", "", "Enter a natural-language financial instruction above."
envelope = compile_intent(text) # AI interpretation layer -- no financial math here
envelope_json = json.dumps(envelope, indent=2)
errors = _schema_errors(envelope)
if errors:
# Should never happen for this baseline; surfaced rather than hidden.
return envelope_json, "", "Schema validation failed:\n" + "\n".join(errors)
status = envelope["status"]
if status != "valid":
reason = envelope.get("reason", "(no reason given)")
label = {"ambiguous": "Ambiguous", "unsupported": "Unsupported"}.get(status, status)
return envelope_json, "", f"**{label}** -- not executed.\n\n{reason}"
try:
FinIRIntent.from_obj(envelope) # structural validation (core package)
execution = execute_intent(_MODEL, envelope) # real FinIR runtime execution
except IntentValidationError as exc:
return envelope_json, "", f"Rejected at execution (semantic validation failed):\n\n{exc}"
if execution.scenario_results is not None:
lines = ["| scenario | ebitda | gross_margin |", "|---|---|---|"]
for name, result in execution.scenario_results.items():
lines.append(
f"| {name} | R{float(result['ebitda']):,.0f} | {float(result['gross_margin']):.3f} |"
)
result_md = "\n".join(lines)
else:
r = execution.result
is_batch = any(op.get("operation") == "range" for op in envelope.get("operations", []))
if is_batch:
ebitda = r["ebitda"]
lines = [
f"- **ebitda range**: R{float(ebitda.min()):,.0f} .. R{float(ebitda.max()):,.0f}",
f"- **batch size**: {ebitda.size:,}",
]
else:
lines = [
f"- **ebitda**: R{float(r['ebitda']):,.0f}",
f"- **gross_margin**: {float(r['gross_margin']):.3f}",
]
if "interest_expense" in r:
lines.append(f"- **interest_expense**: R{float(r['interest_expense']):,.0f}")
if "net_cash_position" in r:
lines.append(f"- **net_cash_position**: R{float(r['net_cash_position']):,.0f}")
lines.append(f"- recomputed: `{sorted(r.recomputed)}`")
lines.append(f"- reused: `{sorted(r.reused)}`")
result_md = "\n".join(lines)
return envelope_json, result_md, "Executed against the real FinIR runtime."
_EXAMPLES = [
"Increase COGS by 4%.",
"Increase opex by R5,000,000.",
"Extend payment terms to 60 days.",
"Revenue falls 8%, COGS rises 3%, and extend payment terms to 60 days.",
"Improve margins next year.",
"Acquire our largest competitor.",
"Increase revenue by USD 2,000,000.",
"Sweep COGS from 300,000,000 to 400,000,000 in 100 steps.",
"Base scenario: no changes. Upside scenario: revenue grows 10%. Downside scenario: revenue falls 8% and COGS rises 5%.",
]
with gr.Blocks(title="FinIR-Intent", analytics_enabled=False) as demo:
gr.Markdown("# FinIR-Intent")
gr.Markdown(_VERSION_BANNER)
gr.Markdown(
"Natural language -> structured **FinIR Intent Contract** envelope -> validated "
"-> executed by the real FinIR runtime. Ambiguous or unsupported requests are "
"refused rather than guessed."
)
with gr.Row():
inp = gr.Textbox(
label="Financial instruction", placeholder="e.g. Increase COGS by 4%.", lines=2
)
run_btn = gr.Button("Compile & execute", variant="primary")
gr.Examples(examples=_EXAMPLES, inputs=inp)
with gr.Row():
envelope_out = gr.Code(label="FinIR Intent envelope (schema v1.0)", language="json")
result_out = gr.Markdown(label="Execution result")
status_out = gr.Markdown()
gr.Markdown(_REFERENCE_MODEL_NOTE)
run_btn.click(run, inputs=inp, outputs=[envelope_out, result_out, status_out])
inp.submit(run, inputs=inp, outputs=[envelope_out, result_out, status_out])
if __name__ == "__main__":
demo.launch()