hermes-agent/optional-skills/finance/excel-author/scripts/recalc.py
Teknium fce58cbe2e
feat(optional-skills): port Anthropic financial-services skills as optional finance bundle (#21180)
Adds 7 optional skills under optional-skills/finance/ adapted from
anthropics/financial-services (Apache-2.0):

  excel-author        — openpyxl conventions: blue/black/green cells,
                        formulas over hardcodes, named ranges, balance
                        checks, sensitivity tables. Ships recalc.py.
  pptx-author         — python-pptx for model-backed decks (pitch,
                        IC memo, earnings note) that bind every number
                        to a source workbook cell.
  dcf-model           — institutional DCF (49KB skill): projections,
                        WACC, terminal value, Bear/Base/Bull scenarios,
                        5x5 sensitivity tables. Ships validate_dcf.py.
  comps-analysis      — comparable company analysis: operating metrics,
                        multiples, statistical benchmarking.
  lbo-model           — leveraged buyout: S&U, debt schedule, cash
                        sweep, exit multiple, IRR/MOIC sensitivity.
  3-statement-model   — fully-integrated IS/BS/CF with balance-check
                        plugs. Ships references/ for formatting,
                        formulas, SEC filings.
  merger-model        — accretion/dilution analysis for M&A.

All seven are optional (not active by default). Users install via
'hermes skills install official/finance/<skill>'.

Hermesification:
- Stripped every Office JS / Office Add-in / mcp__office__*
  branch — skills assume headless openpyxl only.
- Replaced Cowork MCP data-source instructions with 'MCP first (via
  native-mcp), fall back to web_search/web_extract against SEC EDGAR
  and user-provided data'.
- Swapped Claude tool references (Bash, Read, Write, Edit, mcp__*)
  for Hermes-native equivalents and Python library calls.
- Canonical Hermes frontmatter (name/description/version/author/
  license/metadata.hermes.{tags,related_skills}).
- Descriptions tightened to 187-238 chars, trigger-first.
- Attribution preserved: author field credits 'Anthropic (adapted by
  Nous Research)', license: Apache-2.0, each SKILL.md links back to
  the upstream source directory.

Verification:
- All 7 discovered by OptionalSkillSource with source_id='official'
- Bundle fetch includes support files (scripts, references, troubleshooting)
- related_skills cross-refs all resolve within the bundle
- No Claude product / Cowork / Office JS / /mnt/skills leakage
  remains in body text (bounded mentions only in attribution blocks)

Source: https://github.com/anthropics/financial-services (Apache-2.0)
2026-05-07 04:58:39 -07:00

88 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Recalculate an .xlsx file's formulas using LibreOffice headless.
Usage: python recalc.py <path.xlsx> [timeout_seconds]
openpyxl writes formula strings but does not compute them. Downstream scripts
that open the file with data_only=True get None for every formula cell until
something has actually calculated the workbook. Excel does this on open;
headless pipelines need LibreOffice (or similar) to do it explicitly.
Exits 0 on success (workbook recomputed and resaved in place), non-zero on
failure. Writes status JSON to stdout either way.
"""
import json
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
def find_libreoffice() -> str | None:
for cmd in ("libreoffice", "soffice"):
path = shutil.which(cmd)
if path:
return path
return None
def recalc(xlsx_path: str, timeout: int = 60) -> dict:
src = Path(xlsx_path).resolve()
if not src.exists():
return {"status": "error", "error": f"File not found: {src}"}
lo = find_libreoffice()
if lo is None:
return {
"status": "error",
"error": "libreoffice not found on PATH — install it or recalc in a real Excel session",
}
with tempfile.TemporaryDirectory() as td:
try:
subprocess.run(
[
lo,
"--headless",
"--calc",
"--convert-to",
"xlsx",
str(src),
"--outdir",
td,
],
check=True,
capture_output=True,
timeout=timeout,
)
except subprocess.TimeoutExpired:
return {"status": "error", "error": f"libreoffice timed out after {timeout}s"}
except subprocess.CalledProcessError as e:
return {
"status": "error",
"error": f"libreoffice exited {e.returncode}: {e.stderr.decode(errors='replace')[:500]}",
}
produced = Path(td) / src.name
if not produced.exists():
return {"status": "error", "error": "libreoffice did not produce output file"}
shutil.copy(produced, src)
return {"status": "success", "file": str(src)}
def main():
if len(sys.argv) < 2:
print("Usage: python recalc.py <path.xlsx> [timeout_seconds]", file=sys.stderr)
sys.exit(2)
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 60
result = recalc(sys.argv[1], timeout=timeout)
print(json.dumps(result, indent=2))
sys.exit(0 if result["status"] == "success" else 1)
if __name__ == "__main__":
main()