TechScout/techscout/cli.py

279 lines
8.1 KiB
Python
Raw Normal View History

2026-01-22 13:02:09 -05:00
"""
TechScout CLI
Command-line interface for technology scouting.
"""
import argparse
import logging
import sys
import json
from pathlib import Path
from .config import config
from .pipeline.discovery import DiscoveryPipeline
from .pipeline.deep_dive import DeepDivePipeline
from .extraction.llm_client import OllamaClient
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def cmd_discover(args):
"""Run Phase 1 discovery."""
print(f"\n{'='*60}")
print("TECHSCOUT - Phase 1: Discovery")
print(f"{'='*60}\n")
pipeline = DiscoveryPipeline(model=args.model)
print(f"Capability Gap: {args.query}\n")
print("Starting discovery process...\n")
result = pipeline.discover(
capability_gap=args.query,
max_results=args.max_results,
use_llm_scoring=not args.fast
)
if not result.success:
print(f"\nError: {result.error}")
return
# Display results
print(f"\n{'='*60}")
print("DISCOVERY RESULTS")
print(f"{'='*60}\n")
print(f"Query Understanding:")
print(f" {result.decomposition.get('understanding', 'N/A')}\n")
print(f"Technical Domains: {', '.join(result.decomposition.get('technical_domains', []))}\n")
print(f"Source Statistics:")
for source, count in result.source_stats.items():
print(f" - {source}: {count} results")
print(f"\nTotal Results: {result.total_results_found}")
print(f"Search Time: {result.search_duration_seconds:.1f}s\n")
print(f"TOP {len(result.candidates)} CANDIDATES:")
print("-" * 60)
for i, candidate in enumerate(result.candidates[:20], 1):
print(f"\n{i}. {candidate.title[:70]}")
print(f" Organization: {candidate.organization}")
print(f" Source: {candidate.source_type} | Score: {candidate.score:.2f}")
if candidate.trl_estimate:
print(f" TRL: {candidate.trl_estimate}")
if candidate.award_amount:
print(f" Award: ${candidate.award_amount:,.0f}")
print(f" URL: {candidate.url[:60]}...")
print(f"\n\nResults saved to: {config.analyses_dir / f'discovery_{result.id}.json'}")
def cmd_deepdive(args):
"""Run Phase 2 deep dive."""
print(f"\n{'='*60}")
print("TECHSCOUT - Phase 2: Deep Dive")
print(f"{'='*60}\n")
pipeline = DeepDivePipeline(model=args.model)
print(f"Organization: {args.organization}")
print(f"Technology: {args.technology}\n")
print("Starting deep dive analysis...\n")
result = pipeline.deep_dive(
organization=args.organization,
technology_context=args.technology,
capability_gap=args.gap
)
if not result.success:
print(f"\nError: {result.error}")
return
# Display results
print(f"\n{'='*60}")
print("DEEP DIVE RESULTS")
print(f"{'='*60}\n")
print("COMPANY PROFILE")
print("-" * 40)
print(f"Name: {result.company_profile.name}")
print(f"Description: {result.company_profile.description}")
print(f"Headquarters: {result.company_profile.headquarters or 'Unknown'}")
print(f"Founded: {result.company_profile.founded or 'Unknown'}")
if result.company_profile.leadership:
print("Leadership:")
for leader in result.company_profile.leadership[:3]:
print(f" - {leader.get('name', 'Unknown')}: {leader.get('title', 'Unknown')}")
print(f"\nTECHNOLOGY PROFILE")
print("-" * 40)
print(f"Technology: {result.technology_profile.name}")
print(f"TRL Assessment: {result.technology_profile.trl_assessment}")
print(f"Approach: {result.technology_profile.technical_approach[:200]}...")
print(f"Key Capabilities:")
for cap in result.technology_profile.key_capabilities[:5]:
print(f" - {cap}")
print(f"Competitive Advantage: {result.technology_profile.competitive_advantage}")
print(f"\nCONTRACT HISTORY")
print("-" * 40)
print(f"Total Contracts: {result.contract_history.total_contracts}")
print(f"Total Value: ${result.contract_history.total_value:,.0f}")
print(f"Primary Agencies: {', '.join(result.contract_history.primary_agencies[:3])}")
print(f"SBIR Awards: {len(result.contract_history.sbir_awards)}")
print(f"\nASSESSMENT")
print("-" * 40)
print(result.assessment)
print(f"\nRISK FACTORS")
for risk in result.risk_factors:
print(f" - {risk}")
print(f"\nRECOMMENDATION: {result.recommendation}")
print(f"\n\nResults saved to: {config.analyses_dir / f'deepdive_{result.id}.json'}")
def cmd_list(args):
"""List saved analyses."""
print("\nSaved Analyses:")
print("-" * 60)
discoveries = list(config.analyses_dir.glob("discovery_*.json"))
deep_dives = list(config.analyses_dir.glob("deepdive_*.json"))
if discoveries:
print("\nPhase 1 - Discovery:")
for f in sorted(discoveries, reverse=True)[:10]:
with open(f) as fp:
data = json.load(fp)
print(f" {f.stem}: {data.get('capability_gap', 'Unknown')[:50]}...")
if deep_dives:
print("\nPhase 2 - Deep Dive:")
for f in sorted(deep_dives, reverse=True)[:10]:
with open(f) as fp:
data = json.load(fp)
print(f" {f.stem}: {data.get('organization', 'Unknown')}")
if not discoveries and not deep_dives:
print(" No analyses found.")
def cmd_check(args):
"""Check system status."""
print("\nTechScout System Check")
print("-" * 40)
# Check Ollama
client = OllamaClient()
if client.is_available():
print("Ollama: RUNNING")
models = client.list_models()
print(f" Available models: {', '.join(models[:5])}")
else:
print("Ollama: NOT RUNNING")
print(" Please start Ollama: ollama serve")
# Check directories
print(f"\nDirectories:")
print(f" Analyses: {config.analyses_dir} ({'exists' if config.analyses_dir.exists() else 'MISSING'})")
print(f" Exports: {config.exports_dir} ({'exists' if config.exports_dir.exists() else 'MISSING'})")
def main():
parser = argparse.ArgumentParser(
description="TechScout - Technology Scouting & Capability Gap Analysis"
)
subparsers = parser.add_subparsers(dest="command", help="Commands")
# Discover command
discover_parser = subparsers.add_parser(
"discover",
help="Phase 1: Discover technologies for a capability gap"
)
discover_parser.add_argument(
"query",
help="Natural language capability gap description"
)
discover_parser.add_argument(
"--model", "-m",
default="mistral-nemo:12b",
help="Ollama model to use"
)
discover_parser.add_argument(
"--max-results", "-n",
type=int,
default=50,
help="Maximum candidates to return"
)
discover_parser.add_argument(
"--fast",
action="store_true",
help="Skip LLM scoring for faster results"
)
# Deep dive command
deepdive_parser = subparsers.add_parser(
"deepdive",
help="Phase 2: Deep dive into a company/technology"
)
deepdive_parser.add_argument(
"organization",
help="Company name to investigate"
)
deepdive_parser.add_argument(
"--technology", "-t",
required=True,
help="Technology context/description"
)
deepdive_parser.add_argument(
"--gap", "-g",
required=True,
help="Original capability gap"
)
deepdive_parser.add_argument(
"--model", "-m",
default="mistral-nemo:12b",
help="Ollama model to use"
)
# List command
list_parser = subparsers.add_parser(
"list",
help="List saved analyses"
)
# Check command
check_parser = subparsers.add_parser(
"check",
help="Check system status"
)
args = parser.parse_args()
if args.command == "discover":
cmd_discover(args)
elif args.command == "deepdive":
cmd_deepdive(args)
elif args.command == "list":
cmd_list(args)
elif args.command == "check":
cmd_check(args)
else:
parser.print_help()
if __name__ == "__main__":
main()