Wire up collect and parse CLI commands to actual engines
This commit is contained in:
@@ -224,10 +224,28 @@ def collect(ctx, source, url, name, profile_path, limit, since, date_range, upda
|
|||||||
console.print("[yellow]No source specified.[/yellow] Use --source, --url, or --profile.")
|
console.print("[yellow]No source specified.[/yellow] Use --source, --url, or --profile.")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
label = source or url or profile_path
|
from collectors.profile_loader import load_profile, create_adhoc_profile
|
||||||
|
from collectors.custom import CustomSource
|
||||||
|
from utils.cache import DownloadCache
|
||||||
|
from utils.http_limiter import HTTPRateLimiter
|
||||||
|
|
||||||
|
# Load profile
|
||||||
|
try:
|
||||||
|
if profile_path:
|
||||||
|
profile = load_profile(profile_path)
|
||||||
|
elif url:
|
||||||
|
pname = name or url.split('/')[2].replace('.', '_')
|
||||||
|
profile = create_adhoc_profile(url, pname)
|
||||||
|
else:
|
||||||
|
profile = load_profile(source)
|
||||||
|
except (FileNotFoundError, ValueError) as e:
|
||||||
|
console.print(f"[red]Profile error: {e}[/red]")
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
label = profile['name']
|
||||||
console.print(Panel(
|
console.print(Panel(
|
||||||
f"[bold cyan]Collect[/bold cyan]\n\n"
|
f"[bold cyan]Collect[/bold cyan]\n\n"
|
||||||
f" Source : {label}\n"
|
f" Source : {label} — {profile.get('description', '')}\n"
|
||||||
f" Limit : {limit or 'all'}\n"
|
f" Limit : {limit or 'all'}\n"
|
||||||
f" Since : {since or 'n/a'}\n"
|
f" Since : {since or 'n/a'}\n"
|
||||||
f" Range : {' to '.join(date_range) if date_range else 'n/a'}\n"
|
f" Range : {' to '.join(date_range) if date_range else 'n/a'}\n"
|
||||||
@@ -235,9 +253,40 @@ def collect(ctx, source, url, name, profile_path, limit, since, date_range, upda
|
|||||||
title="[bold]mosaic collect[/bold]",
|
title="[bold]mosaic collect[/bold]",
|
||||||
border_style="cyan",
|
border_style="cyan",
|
||||||
))
|
))
|
||||||
console.print(
|
|
||||||
"\n[dim]Collection not yet implemented — see collectors/[/dim]"
|
db = open_db(ctx.obj["db_path"])
|
||||||
)
|
try:
|
||||||
|
cache = DownloadCache(os.path.join(ctx.obj["output_dir"], "raw"))
|
||||||
|
cfg = ctx.obj["cfg"]
|
||||||
|
http_cfg = {
|
||||||
|
'timeout': cfg.get('http', 'timeout', fallback='30'),
|
||||||
|
'max_retries': cfg.get('http', 'max_retries', fallback='3'),
|
||||||
|
'user_agent': cfg.get('http', 'user_agent', fallback='Mozilla/5.0 (compatible; research-crawler/1.0)'),
|
||||||
|
}
|
||||||
|
collector = CustomSource(profile, db, cache, config=http_cfg)
|
||||||
|
|
||||||
|
since_date = since
|
||||||
|
until_date = None
|
||||||
|
if date_range:
|
||||||
|
since_date, until_date = date_range
|
||||||
|
|
||||||
|
stats = collector.collect(
|
||||||
|
since=since_date, until=until_date,
|
||||||
|
limit=limit, update=update,
|
||||||
|
)
|
||||||
|
|
||||||
|
table = Table(title="Collection Results", box=box.ROUNDED)
|
||||||
|
table.add_column("Metric", style="bold")
|
||||||
|
table.add_column("Count", justify="right")
|
||||||
|
table.add_row("Discovered", str(stats.get('discovered', 0)))
|
||||||
|
table.add_row("Downloaded", str(stats.get('downloaded', 0)))
|
||||||
|
table.add_row("Already cached", str(stats.get('cached', 0)))
|
||||||
|
table.add_row("Failed", str(stats.get('failed', 0)))
|
||||||
|
if update:
|
||||||
|
table.add_row("Updated", str(stats.get('updated', 0)))
|
||||||
|
console.print(table)
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -253,6 +302,9 @@ def parse(ctx, source, retry_failed):
|
|||||||
source = source or ctx.obj.get("source")
|
source = source or ctx.obj.get("source")
|
||||||
retry_failed = retry_failed or ctx.obj.get("retry_failed", False)
|
retry_failed = retry_failed or ctx.obj.get("retry_failed", False)
|
||||||
|
|
||||||
|
from parsers.base import ParserManager
|
||||||
|
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
|
||||||
|
|
||||||
console.print(Panel(
|
console.print(Panel(
|
||||||
f"[bold green]Parse[/bold green]\n\n"
|
f"[bold green]Parse[/bold green]\n\n"
|
||||||
f" Source : {source or 'all'}\n"
|
f" Source : {source or 'all'}\n"
|
||||||
@@ -260,9 +312,80 @@ def parse(ctx, source, retry_failed):
|
|||||||
title="[bold]mosaic parse[/bold]",
|
title="[bold]mosaic parse[/bold]",
|
||||||
border_style="green",
|
border_style="green",
|
||||||
))
|
))
|
||||||
console.print(
|
|
||||||
"\n[dim]Parsing not yet implemented — see parsers/[/dim]"
|
db = open_db(ctx.obj["db_path"])
|
||||||
)
|
try:
|
||||||
|
cfg = ctx.obj["cfg"]
|
||||||
|
parser = ParserManager(
|
||||||
|
memory_limit_mb=int(cfg.get('parser', 'subprocess_memory_limit_mb', fallback='512')),
|
||||||
|
timeout_seconds=int(cfg.get('parser', 'subprocess_timeout_seconds', fallback='60')),
|
||||||
|
min_chars_per_page=int(cfg.get('parser', 'min_chars_per_page', fallback='50')),
|
||||||
|
max_pdf_pages=int(cfg.get('parser', 'max_pdf_pages', fallback='200')),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get documents to parse
|
||||||
|
if source:
|
||||||
|
docs = db.get_documents_by_source(source, status='CACHED')
|
||||||
|
else:
|
||||||
|
docs = db.get_documents_by_source('', status='CACHED')
|
||||||
|
# Get all cached docs across sources
|
||||||
|
cursor = db.conn.execute(
|
||||||
|
"SELECT * FROM documents WHERE status = 'CACHED' AND parse_status IN ('PENDING', ?)",
|
||||||
|
('PARSE_FAILED' if retry_failed else '__NONE__',)
|
||||||
|
)
|
||||||
|
from storage.models import Document
|
||||||
|
docs = [Document.from_row(row) for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
if not docs:
|
||||||
|
console.print("[yellow]No documents to parse.[/yellow]")
|
||||||
|
return
|
||||||
|
|
||||||
|
parsed = 0
|
||||||
|
failed = 0
|
||||||
|
needs_ocr = 0
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(), TextColumn("{task.description}"),
|
||||||
|
BarColumn(), TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task(f"Parsing {len(docs)} docs", total=len(docs))
|
||||||
|
for doc in docs:
|
||||||
|
if doc.parse_status in ('PARSED', 'NEEDS_OCR') and not retry_failed:
|
||||||
|
progress.advance(task)
|
||||||
|
continue
|
||||||
|
|
||||||
|
db.update_document_status(doc.id, parse_status='IN_PROGRESS')
|
||||||
|
result = parser.parse(doc.raw_path, doc.doc_type, doc.content_hash)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
doc.text = result.text
|
||||||
|
doc.title = result.title or doc.title
|
||||||
|
doc.char_count = result.char_count
|
||||||
|
if result.metadata:
|
||||||
|
doc.metadata.update(result.metadata)
|
||||||
|
ps = 'NEEDS_OCR' if result.needs_ocr else 'PARSED'
|
||||||
|
doc.parse_status = ps
|
||||||
|
db.insert_document(doc)
|
||||||
|
db.update_document_status(doc.id, parse_status=ps)
|
||||||
|
if result.needs_ocr:
|
||||||
|
needs_ocr += 1
|
||||||
|
else:
|
||||||
|
parsed += 1
|
||||||
|
else:
|
||||||
|
db.update_document_status(doc.id, parse_status='PARSE_FAILED')
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
progress.advance(task)
|
||||||
|
|
||||||
|
table = Table(title="Parse Results", box=box.ROUNDED)
|
||||||
|
table.add_column("Metric", style="bold")
|
||||||
|
table.add_column("Count", justify="right")
|
||||||
|
table.add_row("Parsed", str(parsed))
|
||||||
|
table.add_row("Needs OCR", str(needs_ocr))
|
||||||
|
table.add_row("Failed", str(failed))
|
||||||
|
console.print(table)
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user