205 lines
7.2 KiB
Python
205 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from datetime import datetime, UTC
|
|
|
|
from intel_core import (
|
|
analyze_events,
|
|
validate_analyze_payload,
|
|
validate_collect_payload,
|
|
validate_run_payload,
|
|
)
|
|
from search_provider import (
|
|
InvalidSearchInputError,
|
|
MissingAPIKeyError,
|
|
SearchProviderError,
|
|
UnsupportedProviderError,
|
|
UpstreamHTTPError,
|
|
collect_events,
|
|
)
|
|
|
|
|
|
ERROR_TEMPLATE = {
|
|
"success": False,
|
|
"code": "invalid_input",
|
|
"message": "",
|
|
"data": {},
|
|
"meta": {},
|
|
"errors": [],
|
|
}
|
|
|
|
|
|
def _now_iso() -> str:
|
|
return datetime.now(UTC).isoformat()
|
|
|
|
|
|
def _emit(data: dict, pretty: bool, stream=None):
|
|
print(json.dumps(data, ensure_ascii=False, indent=2 if pretty else None), file=stream or sys.stdout)
|
|
|
|
|
|
def _error_response(code: str, message: str, errors: list[str] | None = None) -> dict:
|
|
return {
|
|
**ERROR_TEMPLATE,
|
|
"code": code,
|
|
"message": message,
|
|
"meta": {"generated_at": _now_iso()},
|
|
"errors": errors or [],
|
|
}
|
|
|
|
|
|
def _load_payload(raw: str) -> dict:
|
|
return json.loads(raw)
|
|
|
|
|
|
def _override_search(payload: dict, args: argparse.Namespace) -> dict:
|
|
payload.setdefault("data", {})
|
|
payload["data"].setdefault("search", {})
|
|
if getattr(args, "provider", None):
|
|
payload["data"]["search"]["provider"] = args.provider
|
|
if getattr(args, "freshness", None):
|
|
payload["data"]["search"]["freshness"] = args.freshness
|
|
if getattr(args, "count", None) is not None:
|
|
payload["data"]["search"]["count"] = args.count
|
|
if getattr(args, "max_events", None) is not None:
|
|
payload["max_events"] = args.max_events
|
|
return payload
|
|
|
|
|
|
def cmd_collect(args: argparse.Namespace):
|
|
payload = _override_search(_load_payload(args.input_json), args)
|
|
errors = validate_collect_payload(payload)
|
|
if errors:
|
|
_emit(_error_response("invalid_input", "payload invalid", errors), args.pretty, sys.stderr)
|
|
raise SystemExit(1)
|
|
events = collect_events(payload)
|
|
result = {
|
|
"success": True,
|
|
"code": "ok",
|
|
"message": "events collected",
|
|
"data": {"developments": events},
|
|
"meta": {"generated_at": _now_iso(), "source_count": len(events)},
|
|
"errors": [],
|
|
}
|
|
_emit(result, args.pretty)
|
|
|
|
|
|
def cmd_analyze(args: argparse.Namespace):
|
|
payload = _override_search(_load_payload(args.input_json), args)
|
|
errors = validate_analyze_payload(payload)
|
|
if errors:
|
|
_emit(_error_response("invalid_input", "payload invalid", errors), args.pretty, sys.stderr)
|
|
raise SystemExit(1)
|
|
events = payload.get("data", {}).get("developments", [])
|
|
data = analyze_events(payload, events)
|
|
result = {
|
|
"success": True,
|
|
"code": "ok",
|
|
"message": "intelligence analyzed",
|
|
"data": data,
|
|
"meta": {
|
|
"generated_at": _now_iso(),
|
|
"raw_count": data.get("stats", {}).get("raw_count", len(events)),
|
|
"dedup_count": data.get("stats", {}).get("dedup_count", len(events)),
|
|
"returned_count": data.get("stats", {}).get("returned_count", len(data.get("developments", []))),
|
|
},
|
|
"errors": [],
|
|
}
|
|
if args.output == "markdown":
|
|
print(data["markdown"])
|
|
return
|
|
_emit(result, args.pretty)
|
|
|
|
|
|
def cmd_run(args: argparse.Namespace):
|
|
payload = _override_search(_load_payload(args.input_json), args)
|
|
errors = validate_run_payload(payload)
|
|
if errors:
|
|
_emit(_error_response("invalid_input", "payload invalid", errors), args.pretty, sys.stderr)
|
|
raise SystemExit(1)
|
|
events = collect_events(payload)
|
|
data = analyze_events(payload, events)
|
|
result = {
|
|
"success": True,
|
|
"code": "ok",
|
|
"message": "intelligence brief generated",
|
|
"data": data,
|
|
"meta": {
|
|
"generated_at": _now_iso(),
|
|
"raw_count": data.get("stats", {}).get("raw_count", len(events)),
|
|
"dedup_count": data.get("stats", {}).get("dedup_count", len(events)),
|
|
"returned_count": data.get("stats", {}).get("returned_count", len(data.get("developments", []))),
|
|
},
|
|
"errors": [],
|
|
}
|
|
if args.output == "markdown":
|
|
print(data["markdown"])
|
|
return
|
|
_emit(result, args.pretty)
|
|
|
|
|
|
def cmd_plan_recurring(args: argparse.Namespace):
|
|
payload = _override_search(_load_payload(args.input_json), args)
|
|
errors = validate_run_payload(payload)
|
|
if errors:
|
|
_emit(_error_response("invalid_input", "payload invalid", errors), args.pretty, sys.stderr)
|
|
raise SystemExit(1)
|
|
data = analyze_events(payload, [])
|
|
result = {
|
|
"success": True,
|
|
"code": "ok",
|
|
"message": "recurring plan generated",
|
|
"data": {"schedule_payload": data["schedule_payload"]},
|
|
"meta": {"generated_at": _now_iso()},
|
|
"errors": [],
|
|
}
|
|
_emit(result, args.pretty)
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(description="Generate competitor intelligence")
|
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
for name in ["collect", "analyze", "run", "plan-recurring"]:
|
|
sub = subparsers.add_parser(name)
|
|
sub.add_argument("--input-json", required=True)
|
|
sub.add_argument("--provider")
|
|
sub.add_argument("--freshness")
|
|
sub.add_argument("--count", type=int)
|
|
sub.add_argument("--max-events", type=int)
|
|
sub.add_argument("--pretty", action="store_true")
|
|
if name in {"analyze", "run"}:
|
|
sub.add_argument("--output", choices=["json", "markdown"], default="json")
|
|
subparsers.choices["collect"].set_defaults(func=cmd_collect)
|
|
subparsers.choices["analyze"].set_defaults(func=cmd_analyze)
|
|
subparsers.choices["run"].set_defaults(func=cmd_run)
|
|
subparsers.choices["plan-recurring"].set_defaults(func=cmd_plan_recurring)
|
|
return parser
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
try:
|
|
args.func(args)
|
|
except json.JSONDecodeError as exc:
|
|
_emit(_error_response("invalid_input", f"invalid json: {exc}", [str(exc)]), getattr(args, "pretty", False), sys.stderr)
|
|
raise SystemExit(1)
|
|
except MissingAPIKeyError as exc:
|
|
_emit(_error_response("missing_api_key", str(exc), [str(exc)]), getattr(args, "pretty", False), sys.stderr)
|
|
raise SystemExit(1)
|
|
except UnsupportedProviderError as exc:
|
|
_emit(_error_response("unsupported_provider", str(exc), [str(exc)]), getattr(args, "pretty", False), sys.stderr)
|
|
raise SystemExit(1)
|
|
except InvalidSearchInputError as exc:
|
|
_emit(_error_response("invalid_input", str(exc), [str(exc)]), getattr(args, "pretty", False), sys.stderr)
|
|
raise SystemExit(1)
|
|
except UpstreamHTTPError as exc:
|
|
_emit(_error_response("upstream_http_error", str(exc), [str(exc)]), getattr(args, "pretty", False), sys.stderr)
|
|
raise SystemExit(1)
|
|
except SearchProviderError as exc:
|
|
_emit(_error_response("provider_error", str(exc), [str(exc)]), getattr(args, "pretty", False), sys.stderr)
|
|
raise SystemExit(1)
|
|
except Exception as exc:
|
|
_emit(_error_response("internal_error", "unexpected error", [str(exc)]), getattr(args, "pretty", False), sys.stderr)
|
|
raise SystemExit(1)
|