FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
55 lines
1.8 KiB
Python
55 lines
1.8 KiB
Python
"""Task B: fact and signal extraction. Pulls discrete, evidence-anchored
|
|
signals (hiring, partnerships, leadership, financial, etc.) out of a single
|
|
document - the atomic units Task C (synthesis) and Task D (report
|
|
generation) later combine."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.analysis.llm.base import LLMProvider
|
|
from app.prompts.base import build_user_prompt
|
|
|
|
SYSTEM_PROMPT = (
|
|
"You are a competitive intelligence analyst extracting discrete factual signals from a "
|
|
"single document. Extract only what the text actually states or clearly implies - never "
|
|
"add outside knowledge. Every signal must include the exact passage that supports it."
|
|
)
|
|
|
|
_SIGNAL_TYPES = (
|
|
"event, entity, date, location, product, leadership, partnership, hiring, investment, "
|
|
"manufacturing, technology, financial, regulatory, sentiment"
|
|
)
|
|
|
|
|
|
class ExtractedSignal(BaseModel):
|
|
signal_type: str = Field(description=f"One of: {_SIGNAL_TYPES}")
|
|
description: str
|
|
supporting_passage: str = Field(description="The exact quote from the document backing this")
|
|
date: str | None = None
|
|
entities: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ExtractionResult(BaseModel):
|
|
signals: list[ExtractedSignal] = Field(default_factory=list)
|
|
|
|
|
|
async def extract_signals(
|
|
llm: LLMProvider,
|
|
*,
|
|
document_title: str | None,
|
|
document_url: str,
|
|
document_text: str,
|
|
) -> ExtractionResult:
|
|
evidence = {
|
|
"document_title": document_title,
|
|
"document_url": document_url,
|
|
"document_text": document_text[:6000],
|
|
}
|
|
user_prompt = build_user_prompt(
|
|
"Extract every discrete factual signal from this document, each anchored to its "
|
|
"exact supporting passage.",
|
|
evidence,
|
|
)
|
|
return await llm.generate_structured(SYSTEM_PROMPT, user_prompt, ExtractionResult)
|