Backfill sparse report sections, add enrichment section refresh, and bootstrap first-admin
Reports: the LLM reliably used company_enrichment for prose fields but inconsistently populated the parallel Finding-list/string-list fields from the same evidence, even with progressively more explicit prompting. Add a code-level backfill (products, recent developments, financial signals, strategic initiatives, regulatory signals, risks/opportunities mirrored from SWOT, unknowns, monitoring recommendations) that only ever fills in what the model left empty, never overwrites what it produced. Enrichment tab: reorder sections (Products/Recent updates before Customers/Competitors) and add a per-section "Refresh" button that re-fetches just one of NinjaPear's six independent per-company endpoints when it came back empty - confirmed live that a data-coverage gap (e.g. Amazon returning no products) is real provider behavior, not a bug. Auth: the first account registered on a deployment with zero existing admins is now auto-promoted to admin, closing the chicken-and-egg gap where the only path to admin access was direct DB access. Self-heals if the last admin ever deletes their account. Also bumps nginx's proxy_read_timeout for api.ciagent.org to cover the enrichment refresh's synchronous funding-endpoint call (up to 5 minutes per NinjaPear's docs). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -63,6 +63,40 @@ def test_register_rejects_weak_password(client):
|
||||
assert resp.status_code == 422
|
||||
|
||||
|
||||
async def test_first_registration_on_an_admin_less_deployment_becomes_admin(db_session, settings):
|
||||
"""Bootstraps admin access on a fresh deployment - without this, the
|
||||
only way to ever get an admin account is direct DB access. Patches
|
||||
count_admins() to simulate a genuinely admin-less deployment rather
|
||||
than manipulating the shared session-wide test DB's real admin count
|
||||
(conftest.py seeds one admin precisely so ordinary registrations in
|
||||
other tests never accidentally trip this path)."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from app.repositories.user_repository import UserRepository
|
||||
from app.schemas.auth import RegisterRequest
|
||||
from app.services.auth_service import register
|
||||
|
||||
payload = RegisterRequest(
|
||||
email=_unique_email(), password="correct-horse-1", display_name="First User"
|
||||
)
|
||||
with patch.object(UserRepository, "count_admins", return_value=0):
|
||||
user = await register(db_session, settings, "127.0.0.1", payload)
|
||||
|
||||
assert user.is_admin is True
|
||||
|
||||
|
||||
async def test_registration_after_an_admin_already_exists_is_not_promoted(db_session, settings):
|
||||
from app.schemas.auth import RegisterRequest
|
||||
from app.services.auth_service import register
|
||||
|
||||
payload = RegisterRequest(
|
||||
email=_unique_email(), password="correct-horse-1", display_name="Second User"
|
||||
)
|
||||
user = await register(db_session, settings, "127.0.0.1", payload)
|
||||
|
||||
assert user.is_admin is False
|
||||
|
||||
|
||||
def test_login_wrong_password_rejected(client):
|
||||
email = _unique_email()
|
||||
client.post(
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _register_and_login(client) -> dict[str, str]:
|
||||
@@ -252,3 +255,73 @@ def test_create_company_does_not_enqueue_enrichment_without_a_key(client):
|
||||
assert resp.status_code == 201
|
||||
mock_delay.assert_not_called()
|
||||
assert resp.json()["enrichment"] is None
|
||||
|
||||
|
||||
def test_refresh_enrichment_section_requires_authentication(client):
|
||||
resp = client.post(f"/api/v1/companies/{uuid.uuid4()}/enrichment/sections/products/refresh")
|
||||
assert resp.status_code == 401
|
||||
|
||||
|
||||
def test_refresh_enrichment_section_rejects_an_unknown_section(client):
|
||||
headers = _register_and_login(client)
|
||||
company = _create_company(client, headers).json()
|
||||
|
||||
resp = client.post(
|
||||
f"/api/v1/companies/{company['id']}/enrichment/sections/not_a_section/refresh",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert resp.status_code == 422
|
||||
|
||||
|
||||
def test_refresh_enrichment_section_404s_without_an_enrichment_record(client):
|
||||
headers = _register_and_login(client)
|
||||
company = _create_company(client, headers).json() # no NINJAPEAR_API_KEY -> no record
|
||||
|
||||
resp = client.post(
|
||||
f"/api/v1/companies/{company['id']}/enrichment/sections/products/refresh",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_refresh_enrichment_section_is_scoped_to_owner(client):
|
||||
owner_headers = _register_and_login(client)
|
||||
other_headers = _register_and_login(client)
|
||||
company = _create_company(client, owner_headers).json()
|
||||
|
||||
resp = client.post(
|
||||
f"/api/v1/companies/{company['id']}/enrichment/sections/products/refresh",
|
||||
headers=other_headers,
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_enrichment_section_updates_just_that_section(client, db_session, settings):
|
||||
from app.enrichment.base import Product
|
||||
from app.enrichment.mock import MockEnrichmentProvider
|
||||
from app.repositories.company_repository import CompanyRepository
|
||||
from app.services.enrichment_service import enrich_company
|
||||
|
||||
headers = _register_and_login(client)
|
||||
company_id = _create_company(client, headers).json()["id"]
|
||||
|
||||
company = await CompanyRepository(db_session).get_by_id(uuid.UUID(company_id))
|
||||
await enrich_company(db_session, settings, MockEnrichmentProvider(), company)
|
||||
|
||||
class _ProductsProvider(MockEnrichmentProvider):
|
||||
async def get_products(self, name, website):
|
||||
return [Product(name="Acme Pay", description="Payments product")]
|
||||
|
||||
with patch("app.api.v1.companies.get_enrichment_provider", return_value=_ProductsProvider()):
|
||||
resp = client.post(
|
||||
f"/api/v1/companies/{company_id}/enrichment/sections/products/refresh",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
products = resp.json()["enrichment"]["data"]["products"]
|
||||
assert products == [{"name": "Acme Pay", "description": "Payments product", "category": None}]
|
||||
|
||||
@@ -7,24 +7,31 @@ import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.errors import NotFoundError
|
||||
from app.enrichment.base import (
|
||||
CompanyDetails,
|
||||
CompanyFunding,
|
||||
LeadershipMember,
|
||||
Product,
|
||||
)
|
||||
from app.models.company import Company
|
||||
from app.models.enums import EnrichmentStatus
|
||||
from app.repositories.company_enrichment_repository import CompanyEnrichmentRepository
|
||||
from app.services.enrichment_service import enrich_company
|
||||
from app.services.enrichment_service import enrich_company, refresh_section
|
||||
|
||||
|
||||
class _FakeProvider:
|
||||
provider_name = "fake"
|
||||
|
||||
def __init__(
|
||||
self, *, leadership: list[LeadershipMember] | None = None, fail: set[str] | None = None
|
||||
self,
|
||||
*,
|
||||
leadership: list[LeadershipMember] | None = None,
|
||||
products: list[Product] | None = None,
|
||||
fail: set[str] | None = None,
|
||||
):
|
||||
self._leadership = leadership or []
|
||||
self._products = products if products is not None else []
|
||||
self._fail = fail or set()
|
||||
|
||||
async def get_company_details(self, name, website):
|
||||
@@ -50,7 +57,7 @@ class _FakeProvider:
|
||||
async def get_products(self, name, website):
|
||||
if "products" in self._fail:
|
||||
raise RuntimeError("products boom")
|
||||
return []
|
||||
return self._products
|
||||
|
||||
async def get_customers(self, name, website):
|
||||
if "customers" in self._fail:
|
||||
@@ -168,3 +175,92 @@ async def test_upsert_updates_the_same_row_on_a_second_call(db_session, settings
|
||||
stored = await CompanyEnrichmentRepository(db_session).get_for_company(company.id)
|
||||
assert stored.id == first.id
|
||||
assert stored.status == EnrichmentStatus.PARTIAL
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_section_replaces_only_that_section(db_session, settings):
|
||||
company = await _make_company(db_session)
|
||||
await enrich_company(db_session, settings, _FakeProvider(), company) # products come back []
|
||||
|
||||
provider = _FakeProvider(products=[Product(name="Acme Pay", description="Payments product")])
|
||||
enrichment = await refresh_section(db_session, settings, provider, company, "products")
|
||||
|
||||
assert [p["name"] for p in enrichment.data["products"]] == ["Acme Pay"]
|
||||
assert enrichment.data["description"] == "A company." # untouched by the refresh
|
||||
assert enrichment.status == EnrichmentStatus.COMPLETE
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_section_records_a_fresh_failure_without_losing_other_data(
|
||||
db_session, settings
|
||||
):
|
||||
company = await _make_company(db_session)
|
||||
await enrich_company(db_session, settings, _FakeProvider(), company)
|
||||
|
||||
failing_provider = _FakeProvider(fail={"products"})
|
||||
enrichment = await refresh_section(db_session, settings, failing_provider, company, "products")
|
||||
|
||||
assert enrichment.errors["products"] == "products boom"
|
||||
assert enrichment.data["description"] == "A company."
|
||||
assert enrichment.status == EnrichmentStatus.PARTIAL
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_section_clears_a_previously_recorded_error(db_session, settings):
|
||||
company = await _make_company(db_session)
|
||||
await enrich_company(db_session, settings, _FakeProvider(fail={"products"}), company)
|
||||
|
||||
provider = _FakeProvider(products=[Product(name="Acme Pay")])
|
||||
enrichment = await refresh_section(db_session, settings, provider, company, "products")
|
||||
|
||||
assert "products" not in enrichment.errors
|
||||
assert enrichment.status == EnrichmentStatus.COMPLETE
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_section_preserves_per_leader_lookups_on_a_details_refresh(
|
||||
db_session, settings
|
||||
):
|
||||
company = await _make_company(db_session)
|
||||
leadership = [LeadershipMember(name="Jane Doe")]
|
||||
await enrich_company(db_session, settings, _FakeProvider(leadership=leadership), company)
|
||||
|
||||
stored = await CompanyEnrichmentRepository(db_session).get_for_company(company.id)
|
||||
assert stored.data["leadership_team"][0]["work_email"] == "[email protected]"
|
||||
|
||||
# A details refresh re-fetches the roster but not the per-leader lookups -
|
||||
# the previously-found work_email must survive, not silently disappear.
|
||||
provider = _FakeProvider(leadership=[LeadershipMember(name="Jane Doe", title="CEO")])
|
||||
enrichment = await refresh_section(db_session, settings, provider, company, "details")
|
||||
|
||||
member = enrichment.data["leadership_team"][0]
|
||||
assert member["title"] == "CEO"
|
||||
assert member["work_email"] == "[email protected]"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_section_rejects_an_unknown_section(db_session, settings):
|
||||
company = await _make_company(db_session)
|
||||
await enrich_company(db_session, settings, _FakeProvider(), company)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await refresh_section(db_session, settings, _FakeProvider(), company, "not_a_section")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_section_requires_an_existing_enrichment_record(db_session, settings):
|
||||
company = await _make_company(db_session) # enrichment was never run
|
||||
|
||||
with pytest.raises(NotFoundError):
|
||||
await refresh_section(db_session, settings, _FakeProvider(), company, "products")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_section_adds_its_own_credit_cost(db_session, settings):
|
||||
company = await _make_company(db_session)
|
||||
baseline = await enrich_company(db_session, settings, _FakeProvider(), company)
|
||||
baseline_credits = baseline.credits_spent # `upsert` mutates rows in place - snapshot first
|
||||
|
||||
enrichment = await refresh_section(db_session, settings, _FakeProvider(), company, "products")
|
||||
|
||||
assert enrichment.credits_spent == baseline_credits + 3 # products' own credit cost
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
"""_backfill_from_enrichment / _mirror_swot_into_flat_lists: company_enrichment
|
||||
data (products/recent_updates/funding) and the model's own SWOT output are
|
||||
direct, unambiguous sources for products_and_services/recent_developments/
|
||||
financial_signals/risks/opportunities - no additional LLM judgment required.
|
||||
Confirmed live that a real Anthropic call can still leave these fields empty
|
||||
despite explicit prompt instructions to populate them, so this backfill
|
||||
guarantees the data surfaces regardless of what the model chose to do."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.prompts.report_generation import ReportContent, SwotAnalysis
|
||||
from app.prompts.schemas import Finding
|
||||
from app.services.report_service import (
|
||||
_backfill_from_enrichment,
|
||||
_backfill_reflective_sections,
|
||||
_mirror_swot_into_flat_lists,
|
||||
)
|
||||
|
||||
|
||||
def _empty_report() -> ReportContent:
|
||||
return ReportContent(
|
||||
executive_summary="",
|
||||
company_overview="",
|
||||
market_positioning="",
|
||||
customer_sentiment="",
|
||||
competitor_comparison="",
|
||||
swot=SwotAnalysis(),
|
||||
methodology="",
|
||||
limitations="",
|
||||
)
|
||||
|
||||
|
||||
def test_backfills_products_from_enrichment_when_model_left_it_empty():
|
||||
report = _empty_report()
|
||||
enrichment = {
|
||||
"products": [
|
||||
{"name": "Acme Pay", "category": "Payments", "description": "Accept cards online."},
|
||||
{"name": "Acme Billing", "category": "Billing", "description": "Recurring invoices."},
|
||||
]
|
||||
}
|
||||
|
||||
_backfill_from_enrichment(report, enrichment)
|
||||
|
||||
assert [f.headline for f in report.products_and_services] == ["Acme Pay", "Acme Billing"]
|
||||
assert report.products_and_services[0].summary == "Accept cards online."
|
||||
assert report.products_and_services[0].confidence == "confirmed"
|
||||
assert report.products_and_services[0].evidence == []
|
||||
|
||||
|
||||
def test_backfills_recent_developments_from_enrichment_updates():
|
||||
report = _empty_report()
|
||||
enrichment = {
|
||||
"recent_updates": [
|
||||
{
|
||||
"url": "https://acme.example/blog/launch",
|
||||
"date": "2026-01-01",
|
||||
"text": "Acme launches new dashboard",
|
||||
"type": "blog",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
_backfill_from_enrichment(report, enrichment)
|
||||
|
||||
assert len(report.recent_developments) == 1
|
||||
finding = report.recent_developments[0]
|
||||
assert finding.headline == "Acme launches new dashboard"
|
||||
assert finding.date == "2026-01-01"
|
||||
assert finding.evidence[0].url == "https://acme.example/blog/launch"
|
||||
|
||||
|
||||
def test_does_not_overwrite_findings_the_model_already_produced():
|
||||
report = _empty_report()
|
||||
report.products_and_services = [
|
||||
Finding(headline="Model-provided product", summary="From the model itself")
|
||||
]
|
||||
enrichment = {"products": [{"name": "Should not appear", "description": "..."}]}
|
||||
|
||||
_backfill_from_enrichment(report, enrichment)
|
||||
|
||||
assert len(report.products_and_services) == 1
|
||||
assert report.products_and_services[0].headline == "Model-provided product"
|
||||
|
||||
|
||||
def test_no_enrichment_data_leaves_lists_empty():
|
||||
report = _empty_report()
|
||||
|
||||
_backfill_from_enrichment(report, None)
|
||||
|
||||
assert report.products_and_services == []
|
||||
assert report.recent_developments == []
|
||||
|
||||
|
||||
def test_skips_entries_missing_the_required_key():
|
||||
report = _empty_report()
|
||||
enrichment = {
|
||||
"products": [{"category": "No name here"}],
|
||||
"recent_updates": [{"url": "https://acme.example", "date": "2026-01-01"}],
|
||||
}
|
||||
|
||||
_backfill_from_enrichment(report, enrichment)
|
||||
|
||||
assert report.products_and_services == []
|
||||
assert report.recent_developments == []
|
||||
|
||||
|
||||
def test_backfills_financial_signals_from_funding_rounds_and_total():
|
||||
report = _empty_report()
|
||||
enrichment = {
|
||||
"funding": {
|
||||
"total_raised": "9810000000",
|
||||
"rounds": [
|
||||
{
|
||||
"date": "2026-02-01",
|
||||
"amount": None,
|
||||
"investors": ["Thrive Capital", "Coatue Management"],
|
||||
"round_name": "SECONDARY_SALE",
|
||||
},
|
||||
{
|
||||
"date": "2023-03-01",
|
||||
"amount": "6870000000",
|
||||
"investors": ["Thrive Capital", "Andreessen Horowitz"],
|
||||
"round_name": "SERIES_I",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
_backfill_from_enrichment(report, enrichment)
|
||||
|
||||
assert len(report.financial_signals) == 3
|
||||
assert report.financial_signals[0].headline == "Total funding raised: $9,810,000,000"
|
||||
assert report.financial_signals[1].headline == "Secondary Sale"
|
||||
assert "Thrive Capital" in report.financial_signals[1].summary
|
||||
assert report.financial_signals[2].headline == "Series I - $6,870,000,000"
|
||||
assert report.financial_signals[2].date == "2023-03-01"
|
||||
|
||||
|
||||
def test_no_funding_data_leaves_financial_signals_empty():
|
||||
report = _empty_report()
|
||||
|
||||
_backfill_from_enrichment(report, {"products": []})
|
||||
|
||||
assert report.financial_signals == []
|
||||
|
||||
|
||||
def test_does_not_overwrite_financial_signals_the_model_already_produced():
|
||||
report = _empty_report()
|
||||
report.financial_signals = [Finding(headline="Model-provided signal", summary="From the model")]
|
||||
enrichment = {"funding": {"total_raised": "1000", "rounds": []}}
|
||||
|
||||
_backfill_from_enrichment(report, enrichment)
|
||||
|
||||
assert len(report.financial_signals) == 1
|
||||
assert report.financial_signals[0].headline == "Model-provided signal"
|
||||
|
||||
|
||||
def test_mirrors_swot_threats_into_risks_when_risks_empty():
|
||||
report = _empty_report()
|
||||
report.swot = SwotAnalysis(threats=["Regulatory scrutiny", "New entrants"])
|
||||
|
||||
_mirror_swot_into_flat_lists(report)
|
||||
|
||||
assert report.risks == ["Regulatory scrutiny", "New entrants"]
|
||||
|
||||
|
||||
def test_mirrors_swot_opportunities_into_opportunities_when_empty():
|
||||
report = _empty_report()
|
||||
report.swot = SwotAnalysis(opportunities=["Expand into new markets"])
|
||||
|
||||
_mirror_swot_into_flat_lists(report)
|
||||
|
||||
assert report.opportunities == ["Expand into new markets"]
|
||||
|
||||
|
||||
def test_does_not_overwrite_risks_or_opportunities_the_model_already_produced():
|
||||
report = _empty_report()
|
||||
report.risks = ["Model-provided risk"]
|
||||
report.opportunities = ["Model-provided opportunity"]
|
||||
report.swot = SwotAnalysis(threats=["Should not appear"], opportunities=["Should not appear"])
|
||||
|
||||
_mirror_swot_into_flat_lists(report)
|
||||
|
||||
assert report.risks == ["Model-provided risk"]
|
||||
assert report.opportunities == ["Model-provided opportunity"]
|
||||
|
||||
|
||||
def test_empty_swot_leaves_risks_and_opportunities_empty():
|
||||
report = _empty_report()
|
||||
|
||||
_mirror_swot_into_flat_lists(report)
|
||||
|
||||
assert report.risks == []
|
||||
assert report.opportunities == []
|
||||
|
||||
|
||||
def test_backfills_strategic_initiatives_from_specialties():
|
||||
report = _empty_report()
|
||||
enrichment = {"specialties": ["Payment Processing", "Billing Models"]}
|
||||
|
||||
_backfill_reflective_sections(
|
||||
report, enrichment=enrichment, documents=[], changes=[], company_name="Acme"
|
||||
)
|
||||
|
||||
assert [f.headline for f in report.strategic_initiatives] == [
|
||||
"Focus area: Payment Processing",
|
||||
"Focus area: Billing Models",
|
||||
]
|
||||
assert report.strategic_initiatives[0].confidence == "possible"
|
||||
|
||||
|
||||
def test_regulatory_signals_get_an_insufficient_evidence_placeholder_when_empty():
|
||||
report = _empty_report()
|
||||
|
||||
_backfill_reflective_sections(
|
||||
report, enrichment=None, documents=[], changes=[], company_name="Acme"
|
||||
)
|
||||
|
||||
assert len(report.regulatory_and_legal_signals) == 1
|
||||
assert report.regulatory_and_legal_signals[0].confidence == "insufficient_evidence"
|
||||
|
||||
|
||||
def test_unknowns_reflect_actual_gaps_in_the_evidence_set():
|
||||
report = _empty_report()
|
||||
enrichment = {"funding": {}, "leadership_team": [], "customers": []}
|
||||
|
||||
_backfill_reflective_sections(
|
||||
report, enrichment=enrichment, documents=[], changes=[], company_name="Acme"
|
||||
)
|
||||
|
||||
assert any("financial" in u.lower() for u in report.unknowns_and_missing_data)
|
||||
assert any("leadership" in u.lower() for u in report.unknowns_and_missing_data)
|
||||
assert any("customers" in u.lower() for u in report.unknowns_and_missing_data)
|
||||
assert any("source documents" in u.lower() for u in report.unknowns_and_missing_data)
|
||||
assert any("changes" in u.lower() for u in report.unknowns_and_missing_data)
|
||||
|
||||
|
||||
def test_unknowns_omit_gaps_that_are_actually_covered():
|
||||
report = _empty_report()
|
||||
enrichment = {
|
||||
"funding": {"total_raised": "1000"},
|
||||
"leadership_team": [{"name": "Jane"}],
|
||||
"customers": [{"name": "Acme Corp"}],
|
||||
}
|
||||
|
||||
_backfill_reflective_sections(
|
||||
report,
|
||||
enrichment=enrichment,
|
||||
documents=[{"id": "d1"}],
|
||||
changes=[{"id": "c1"}],
|
||||
company_name="Acme",
|
||||
)
|
||||
|
||||
assert report.unknowns_and_missing_data == []
|
||||
|
||||
|
||||
def test_monitoring_recommendations_populated_when_model_left_empty():
|
||||
report = _empty_report()
|
||||
|
||||
_backfill_reflective_sections(
|
||||
report, enrichment=None, documents=[], changes=[], company_name="Acme"
|
||||
)
|
||||
|
||||
assert len(report.monitoring_recommendations) >= 2
|
||||
assert any("Acme" in r for r in report.monitoring_recommendations)
|
||||
|
||||
|
||||
def test_reflective_sections_do_not_overwrite_model_output():
|
||||
report = _empty_report()
|
||||
report.strategic_initiatives = [Finding(headline="Model theme", summary="From the model")]
|
||||
report.regulatory_and_legal_signals = [Finding(headline="Model signal", summary="From model")]
|
||||
report.unknowns_and_missing_data = ["Model-noted gap"]
|
||||
report.monitoring_recommendations = ["Model recommendation"]
|
||||
enrichment = {"specialties": ["Should not appear"]}
|
||||
|
||||
_backfill_reflective_sections(
|
||||
report, enrichment=enrichment, documents=[], changes=[], company_name="Acme"
|
||||
)
|
||||
|
||||
assert report.strategic_initiatives[0].headline == "Model theme"
|
||||
assert report.regulatory_and_legal_signals[0].headline == "Model signal"
|
||||
assert report.unknowns_and_missing_data == ["Model-noted gap"]
|
||||
assert report.monitoring_recommendations == ["Model recommendation"]
|
||||
Reference in New Issue
Block a user