Catalyst / admin/Bid-Sentinel 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Bid-Sentinel

public

Bid Scrape and Tracking Application with AI Capability

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Bid-Sentinel / bid-sentinel-v2 / backend / app / routers / intelligence.py 6104 B · main
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""Analyst Intelligence endpoints + printable PDF report (analyst/admin only)."""
from fastapi import APIRouter, Depends, Query, Response
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app import ai, intelligence
from app.analysis import get_capability_terms
from app.appsettings import get_ai_enabled
from app.database import get_db
from app.deps import get_current_analyst
from app.models import Accreditation, CustomKeyword, Tender, User
from app.schemas import (
    BuyerItem,
    CapabilityGapItem,
    CertGapItem,
    IntelSummary,
    SalesAngleItem,
    TrendPoint,
    WinLoss,
)
from app.reports import build_intelligence_report, build_management_summary

router = APIRouter(prefix="/intelligence", tags=["intelligence"])


async def _load(db: AsyncSession):
    """Fetch the inputs shared by every intelligence view."""
    tenders = list((await db.execute(select(Tender))).scalars().all())
    capability_terms = await get_capability_terms(db)
    custom_kw = [k for (k,) in (await db.execute(select(CustomKeyword.keyword))).all()]
    accreditations = [a for (a,) in (await db.execute(select(Accreditation.name).order_by(Accreditation.id))).all()]
    demand = intelligence.demand_vocabulary(custom_kw)
    return tenders, capability_terms, demand, accreditations


@router.get("/summary", response_model=IntelSummary)
async def get_summary(db: AsyncSession = Depends(get_db), _a: User = Depends(get_current_analyst)):
    tenders, terms, demand, accred = await _load(db)
    return IntelSummary(**intelligence.summary(tenders, terms, demand, accred))


@router.get("/capability-gaps", response_model=list[CapabilityGapItem])
async def get_capability_gaps(db: AsyncSession = Depends(get_db), _a: User = Depends(get_current_analyst)):
    tenders, terms, demand, _ = await _load(db)
    return intelligence.capability_gaps(tenders, terms, demand)


@router.get("/cert-gaps", response_model=list[CertGapItem])
async def get_cert_gaps(db: AsyncSession = Depends(get_db), _a: User = Depends(get_current_analyst)):
    tenders, _, _, accred = await _load(db)
    return intelligence.cert_gaps(tenders, accred)


@router.get("/buyers", response_model=list[BuyerItem])
async def get_buyers(db: AsyncSession = Depends(get_db), _a: User = Depends(get_current_analyst)):
    tenders, _, _, _ = await _load(db)
    return intelligence.buyer_profiles(tenders)


@router.get("/winloss", response_model=WinLoss)
async def get_winloss(db: AsyncSession = Depends(get_db), _a: User = Depends(get_current_analyst)):
    tenders, _, _, _ = await _load(db)
    return intelligence.win_loss(tenders)


@router.get("/trends", response_model=list[TrendPoint])
async def get_trends(db: AsyncSession = Depends(get_db), _a: User = Depends(get_current_analyst)):
    tenders, _, _, _ = await _load(db)
    return intelligence.trends(tenders)


@router.get("/angles", response_model=list[SalesAngleItem])
async def get_angles(
    db: AsyncSession = Depends(get_db),
    _a: User = Depends(get_current_analyst),
    use_ai: bool = Query(default=False),
    limit: int = Query(default=5, le=15),
):
    """Sales angles for the top target buyers (deterministic, or AI when requested)."""
    tenders, terms, _, accred = await _load(db)
    buyers = intelligence.buyer_profiles(tenders)
    targets = [b for b in buyers if b["is_target"]] or buyers
    ai_on = use_ai and await get_ai_enabled(db) and ai.is_available()

    out: list[SalesAngleItem] = []
    for buyer in targets[:limit]:
        angle, source = intelligence.deterministic_angle(buyer, accred), "rules"
        if ai_on:
            written = await ai.sales_angle(intelligence.angle_context(buyer, terms, accred))
            if written:
                angle, source = written, "AI"
        out.append(SalesAngleItem(subject=buyer["buyer"], angle=angle, source=source))
    return out


@router.get("/management-summary.pdf")
async def management_summary_pdf(
    db: AsyncSession = Depends(get_db),
    _a: User = Depends(get_current_analyst),
):
    """Executive pipeline report: tracked / open / being-bid-on opportunities,
    pipeline value, and capability + certification gaps."""
    tenders, terms, demand, accred = await _load(db)
    pdf = build_management_summary(
        stats=intelligence.management_stats(tenders),
        bidding_list=intelligence.bidding_opportunities(tenders),
        capability_gaps=intelligence.capability_gaps(tenders, terms, demand),
        cert_gaps=intelligence.cert_gaps(tenders, accred),
    )
    return Response(
        content=pdf,
        media_type="application/pdf",
        headers={"Content-Disposition": 'attachment; filename="bid-sentinel-management-summary.pdf"'},
    )


@router.get("/report.pdf")
async def report_pdf(
    db: AsyncSession = Depends(get_db),
    _a: User = Depends(get_current_analyst),
    use_ai: bool = Query(default=False),
):
    """Printable Intelligence Report PDF."""
    tenders, terms, demand, accred = await _load(db)
    buyers = intelligence.buyer_profiles(tenders)
    targets = [b for b in buyers if b["is_target"]] or buyers

    angles = []
    ai_on = use_ai and await get_ai_enabled(db) and ai.is_available()
    for buyer in targets[:5]:
        text = intelligence.deterministic_angle(buyer, accred)
        source = "rules"
        if ai_on:
            written = await ai.sales_angle(intelligence.angle_context(buyer, terms, accred))
            if written:
                text, source = written, "AI"
        angles.append({"subject": buyer["buyer"], "angle": text, "source": source})

    pdf = build_intelligence_report(
        summary=intelligence.summary(tenders, terms, demand, accred),
        capability_gaps=intelligence.capability_gaps(tenders, terms, demand),
        cert_gaps=intelligence.cert_gaps(tenders, accred),
        buyers=buyers,
        winloss=intelligence.win_loss(tenders),
        angles=angles,
    )
    return Response(
        content=pdf,
        media_type="application/pdf",
        headers={"Content-Disposition": 'attachment; filename="bid-sentinel-intelligence.pdf"'},
    )