Files
last30days-skill/tests/test_save_raw_per_entity.py
T
Matt Van Horn 949bcf8942 feat: vs mode N full passes + --competitors auto-discovery + (/Last30Days) title (#312)
* feat: vs mode runs N full passes; --competitors wraps vs with auto-discovery

Unifies vs-mode and --competitors onto one fanout architecture. A topic
containing "vs" / "versus" now runs N full pipeline.run() calls in parallel
(reverting the one-pass latency optimization that removed per-entity
depth); --competitors becomes a SKILL.md-level shortcut where the hosting
reasoning model (Claude Code, Codex, Hermes, Gemini) discovers N peers via
its own WebSearch, runs Step 0.55 per entity, and invokes the engine with
a vs-topic + --competitors-plan JSON.

Changed:
- vs-mode: N full passes in parallel via fanout (was 1 merged pass).
- --competitors: SKILL.md shortcut for vs-mode-with-discovery. Engine flag
  kept for headless/cron use. LAW 7-style stderr reframed to lead with the
  hosting-model path (use WebSearch + --competitors-plan) instead of
  BRAVE_API_KEY. Footer BRAVE/SERPER nudge suppressed when --plan or
  --competitors-plan present (hosting model already has WebSearch).

Added:
- --competitors-plan JSON flag: per-entity {x_handle, x_related, subreddits,
  github_user, github_repos, context}. Accepts inline JSON or file path.
  subrun_kwargs_for helper is the single source of truth for per-entity
  kwargs — no closure-default fallthrough from main scope.
- Per-entity save files: each entity's sub-run produces its own
  {slug}-raw.md with a single-row Resolved Entities block.
- --polymarket-keywords filter for ambiguous single-token topics.

Fixed:
- test_competitor_subrun_isolation regression suite locks in 3.0.12's
  no-leak invariant (main flags do not inherit into peer sub-runs).
- Updates test_regression.py for the new comparison-mode payload shape.

Bumps plugin.json to 3.0.13. 1,219 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: comparison title attribution — (Last 30 Days) → (/Last30Days)

User feedback on 3.0.13 dogfood runs (Kanye vs Drake, Mercer Island,
Figma): the comparison-mode synthesis title should attribute to the
slash command rather than restate the date range.

Three SKILL.md occurrences updated. Pure documentation change. Bumps to
3.0.14.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:31:00 -07:00

85 lines
2.9 KiB
Python

# ruff: noqa: E402
"""Tests for per-entity save files when running vs-mode or --competitors.
Each entity's sub-run produces its own {entity-slug}-raw.md. Single-entity
runs unchanged.
"""
from __future__ import annotations
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
def _engine_path() -> Path:
return REPO_ROOT / "scripts" / "last30days.py"
class PerEntitySaveFilesTests(unittest.TestCase):
def _run(self, *argv: str, topic: str) -> tuple[subprocess.CompletedProcess, Path]:
save_dir = Path(tempfile.mkdtemp(prefix="last30days-test-"))
cmd = [
sys.executable,
str(_engine_path()),
topic,
"--mock",
"--emit=md",
"--save-dir", str(save_dir),
*argv,
]
env = {**os.environ, "LAST30DAYS_SKIP_PREFLIGHT": "1"}
result = subprocess.run(cmd, capture_output=True, text=True, env=env)
return result, save_dir
def test_vs_mode_produces_per_entity_files(self):
result, save_dir = self._run(topic="Kanye West vs Drake vs Kendrick Lamar")
self.assertEqual(result.returncode, 0, msg=result.stderr)
files = sorted(save_dir.glob("*-raw.md"))
names = [f.name for f in files]
# Each entity slug should produce a file
self.assertIn("kanye-west-raw.md", names)
self.assertIn("drake-raw.md", names)
self.assertIn("kendrick-lamar-raw.md", names)
def test_competitors_list_produces_per_entity_files(self):
result, save_dir = self._run(
"--competitors-list", "Anthropic,xAI",
topic="OpenAI",
)
self.assertEqual(result.returncode, 0, msg=result.stderr)
files = sorted(save_dir.glob("*-raw.md"))
names = [f.name for f in files]
self.assertIn("openai-raw.md", names)
self.assertIn("anthropic-raw.md", names)
self.assertIn("xai-raw.md", names)
def test_single_entity_run_produces_one_file(self):
result, save_dir = self._run(topic="OpenAI")
self.assertEqual(result.returncode, 0, msg=result.stderr)
files = sorted(save_dir.glob("*-raw.md"))
self.assertEqual(len(files), 1)
self.assertEqual(files[0].name, "openai-raw.md")
def test_per_entity_file_has_resolved_block(self):
result, save_dir = self._run(
"--competitors-list", "Anthropic",
topic="OpenAI",
)
self.assertEqual(result.returncode, 0, msg=result.stderr)
anthropic_file = save_dir / "anthropic-raw.md"
self.assertTrue(anthropic_file.exists())
content = anthropic_file.read_text()
self.assertIn("## Resolved Entities", content)
self.assertIn("**Anthropic**", content)
if __name__ == "__main__":
unittest.main()