LLM-prompt-driven market-research report generation (executive brief, competitive landscape, competitor profile) over infrastructure-ai::AiProvider — industry-agnostic.
| Tier | domain |
| Role | unclassified (baselined) |
| Path | crates/domain/ai-report |
| Edition | 2021 |
| Targets | domain_ai_report |
| Public items | 37 across 9 modules |
| Tests | 25 |
What it is for
LLM-prompt-driven market-research report generation.
Orchestrates a infrastructure_ai::AiProvider to generate three report types — an executive brief, a competitive-landscape report, and a competitor profile — from prompt-template structs with generic numeric/ string fields (competitor counts, ratings, keyword stats, etc.).
Ported from plumber/src/ai/** (Sprint 3.46 crate 21). The source hardcoded plumbing/Phoenix content into its system prompt, its assembled- report headers/footer, and one prompt struct's field name; this crate parameterizes all of that via ReportContext (business vertical + geography + product branding) so the report-assembly engine is industry-agnostic. See CHANGELOG.md for the full list of AC-generic changes and their rationale.
# Example
use infrastructure_ai::StubAiProvider;
use domain_ai_report::{CostRates, ExecutiveBriefData, ReportContext, ReportWriter};
use std::sync::Arc;
# async fn example() -> domain_ai_report::Result<()> {
let context = ReportContext::new("plumbing", "Phoenix, Arizona", "MarketScope");
let mut writer = ReportWriter::new(
Arc::new(StubAiProvider::new(true)),
context,
CostRates::default(),
);
let report = writer
.generate_executive_brief(ExecutiveBriefData {
competitor_count: 10,
avg_rating: 4.4,
avg_reviews: 60.0,
top_3_names: vec"A".into(), "B".into(), "C".into(),
total_pages: 500,
unique_keywords: 100,
keyword_gaps: 12,
performance_issues_pct: 20.0,
top_opportunities: vec"Opp".into(),
top_threats: vec"Threat".into(),
})
.await?;
assert!(report.contains("Phoenix, Arizona plumbing Market"));
# Ok(())
# }
Capabilities
ReportContext
Caller-supplied report context: the business vertical, geography, and
| Item |
|---|
pub struct ReportContext |
ReportContext :: fn new(business_type : impl Into <String>, geography : impl Into <String>, product_name : impl Into <String>,) -> Self |
ReportContext :: fn system_prompt(& self) -> String |
CostRates
Token-cost estimation rates.
| Item |
|---|
pub struct CostRates |
CostRates :: fn new(input_per_million : f64, output_per_million : f64) -> Self |
CostRates :: fn estimate(& self, input_tokens : u32, output_tokens : u32) -> f64 |
CostRates :: fn default() -> Self |
ReportError
Error type for report generation.
| Item |
|---|
pub enum ReportError |
Result
Error type for report generation.
| Item |
|---|
pub type Result<T>: std::result::Result <T, ReportError> |
CompetitorAnalysisPrompt
Competitor Analysis prompt template.
| Item |
|---|
pub struct CompetitorAnalysisPrompt |
CompetitorAnalysisPrompt :: fn render(& self) -> String |
ExecutiveSummaryPrompt
Executive Summary prompt template.
| Item |
|---|
pub struct ExecutiveSummaryPrompt |
ExecutiveSummaryPrompt :: fn render(& self) -> String |
KeywordStats
Market Landscape prompt template.
| Item |
|---|
pub struct KeywordStats |
LeaderInfo
Market Landscape prompt template.
| Item |
|---|
pub struct LeaderInfo |
MarketLandscapePrompt
Market Landscape prompt template.
| Item |
|---|
pub struct MarketLandscapePrompt |
MarketLandscapePrompt :: fn render(& self) -> String |
TechAdoption
Market Landscape prompt template.
| Item |
|---|
pub struct TechAdoption |
TierBreakdown
Market Landscape prompt template.
| Item |
|---|
pub struct TierBreakdown |
CompetitorWeakness
Opportunity Finder prompt template.
| Item |
|---|
pub struct CompetitorWeakness |
ContentGap
Opportunity Finder prompt template.
| Item |
|---|
pub struct ContentGap |
KeywordGap
Opportunity Finder prompt template.
| Item |
|---|
pub struct KeywordGap |
OpportunityFinderPrompt
Opportunity Finder prompt template.
| Item |
|---|
pub struct OpportunityFinderPrompt |
OpportunityFinderPrompt :: fn render(& self) -> String |
TechGap
Opportunity Finder prompt template.
| Item |
|---|
pub struct TechGap |
CompetitorProfileData
Report writer that orchestrates AI analysis and generates reports.
| Item |
|---|
pub struct CompetitorProfileData |
ExecutiveBriefData
Report writer that orchestrates AI analysis and generates reports.
| Item |
|---|
pub struct ExecutiveBriefData |
LandscapeReportData
Report writer that orchestrates AI analysis and generates reports.
| Item |
|---|
pub struct LandscapeReportData |
ReportWriter
Report writer that orchestrates AI analysis and generates reports.
| Item |
|---|
pub struct ReportWriter |
ReportWriter :: fn new(provider : Arc <dyn AiProvider>, context : ReportContext, cost_rates : CostRates,) -> Self |
ReportWriter :: fn tokens_used(& self) ->(u32, u32) |
ReportWriter :: fn estimated_cost(& self) -> f64 |
ReportWriter :: fn provider_name(& self) -> & 'static str |
ReportWriter :: fn is_available(& self) -> bool |
ReportWriter :: async fn generate_executive_brief(& mut self, data : ExecutiveBriefData) -> Result <String> |
ReportWriter :: async fn generate_landscape_report(& mut self, data : LandscapeReportData) -> Result <String> |
ReportWriter :: async fn generate_competitor_profile(& mut self, data : CompetitorProfileData,) -> Result <String> |
How to use it
From this crate's own rustdoc:
use infrastructure_ai::StubAiProvider;
use domain_ai_report::{CostRates, ExecutiveBriefData, ReportContext, ReportWriter};
use std::sync::Arc;
# async fn example() -> domain_ai_report::Result<()> {
let context = ReportContext::new("plumbing", "Phoenix, Arizona", "MarketScope");
let mut writer = ReportWriter::new(
Arc::new(StubAiProvider::new(true)),
context,
CostRates::default(),
);
let report = writer
.generate_executive_brief(ExecutiveBriefData {
competitor_count: 10,
avg_rating: 4.4,
avg_reviews: 60.0,
top_3_names: vec!["A".into(), "B".into(), "C".into()],
total_pages: 500,
unique_keywords: 100,
keyword_gaps: 12,
performance_issues_pct: 20.0,
top_opportunities: vec!["Opp".into()],
top_threats: vec!["Threat".into()],
})
.await?;
assert!(report.contains("Phoenix, Arizona plumbing Market"));
# Ok(())
# }
Module structure
domain_ai_report
contextcosterrorpromptsprompts::competitor_analysisprompts::executive_summaryprompts::market_landscapeprompts::opportunity_finderreport_writer
flowchart TD n_domain_ai_report["domain_ai_report"] n_domain_ai_report --> n_context["context"] n_domain_ai_report --> n_cost["cost"] n_domain_ai_report --> n_error["error"] n_domain_ai_report --> n_prompts["prompts"] n_prompts --> n_prompts__competitor_analysis["competitor_analysis"] n_prompts --> n_prompts__executive_summary["executive_summary"] n_prompts --> n_prompts__market_landscape["market_landscape"] n_prompts --> n_prompts__opportunity_finder["opportunity_finder"] n_domain_ai_report --> n_report_writer["report_writer"]
Public surface
`context`
| Item | What it is |
|---|---|
pub struct ReportContext | The business vertical, geography, and product branding a report is generated for |
ReportContext :: fn new(business_type : impl Into <String>, geography : impl Into <String>, product_name : impl Into <String>,) -> Self | Construct a new report context. |
ReportContext :: fn system_prompt(& self) -> String | Build the system prompt for all market-research completions from this context |
`cost`
| Item | What it is |
|---|---|
pub struct CostRates | Per-million-token pricing used to estimate the dollar cost of a report generation session from token usage |
CostRates :: fn new(input_per_million : f64, output_per_million : f64) -> Self | Construct explicit rates. |
CostRates :: fn estimate(& self, input_tokens : u32, output_tokens : u32) -> f64 | Estimate the dollar cost of the given token usage at these rates. |
CostRates :: fn default() -> Self | Example/default rates only (Claude Sonnet 4 pricing as ported from plumber/src/ai/report_writer.rs's INPUT_COST_PER_MILLION / OUTPUT_COST_PER_MILLION constants) — not authoritative or necessarily current |
`error`
| Item | What it is |
|---|---|
pub enum ReportError | Error type for ReportWriter operations |
pub type Result<T>: std::result::Result <T, ReportError> | Result type for ReportWriter operations. |
`prompts::competitor_analysis`
| Item | What it is |
|---|---|
pub struct CompetitorAnalysisPrompt | Prompt data for a single-competitor deep-dive analysis |
CompetitorAnalysisPrompt :: fn render(& self) -> String | Render the LLM-facing prompt text for this competitor analysis. |
`prompts::executive_summary`
| Item | What it is |
|---|---|
pub struct ExecutiveSummaryPrompt | Prompt data for a market-research executive summary |
ExecutiveSummaryPrompt :: fn render(& self) -> String | Render the LLM-facing prompt text for this executive summary. |
`prompts::market_landscape`
| Item | What it is |
|---|---|
pub struct MarketLandscapePrompt | Prompt data for the competitive-landscape section of a market-research report |
pub struct LeaderInfo | — |
pub struct TierBreakdown | — |
pub struct TechAdoption | — |
pub struct KeywordStats | — |
MarketLandscapePrompt :: fn render(& self) -> String | Render the LLM-facing prompt text for the market-landscape section. |
`prompts::opportunity_finder`
| Item | What it is |
|---|---|
pub struct OpportunityFinderPrompt | Prompt data for a market-opportunity-ranking section |
pub struct KeywordGap | — |
pub struct TechGap | — |
pub struct ContentGap | — |
pub struct CompetitorWeakness | — |
OpportunityFinderPrompt :: fn render(& self) -> String | Render the LLM-facing prompt text for opportunity ranking. |
`report_writer`
| Item | What it is |
|---|---|
pub struct ReportWriter | Report writer that generates AI-powered market-research reports |
pub struct ExecutiveBriefData | Data for generating an executive brief. |
pub struct LandscapeReportData | Data for generating a landscape report |
pub struct CompetitorProfileData | Data for generating a competitor profile |
ReportWriter :: fn new(provider : Arc <dyn AiProvider>, context : ReportContext, cost_rates : CostRates,) -> Self | Create a new report writer |
ReportWriter :: fn tokens_used(& self) ->(u32, u32) | Get total tokens used in this session. |
ReportWriter :: fn estimated_cost(& self) -> f64 | Estimate total cost for this session at this writer's CostRates. |
ReportWriter :: fn provider_name(& self) -> & 'static str | Get the provider name. |
ReportWriter :: fn is_available(& self) -> bool | Check if the provider is available. |
ReportWriter :: async fn generate_executive_brief(& mut self, data : ExecutiveBriefData) -> Result <String> | Generate an Executive Intelligence Brief. |
ReportWriter :: async fn generate_landscape_report(& mut self, data : LandscapeReportData) -> Result <String> | Generate a Competitive Landscape Report. |
ReportWriter :: async fn generate_competitor_profile(& mut self, data : CompetitorProfileData,) -> Result <String> | Generate a Competitor Profile. |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
CompetitorAnalysisPrompt | competitor_analysis::CompetitorAnalysisPrompt |
CostRates | cost::CostRates |
ExecutiveSummaryPrompt | executive_summary::ExecutiveSummaryPrompt |
ReportContext | context::ReportContext |
{CompetitorAnalysisPrompt,CompetitorWeakness,ContentGap,ExecutiveSummaryPrompt,KeywordGap,KeywordStats,LeaderInfo,MarketLandscapePrompt,OpportunityFinderPrompt,TechAdoption,TechGap,TierBreakdown,} | prompts::{CompetitorAnalysisPrompt,CompetitorWeakness,ContentGap,ExecutiveSummaryPrompt,KeywordGap,KeywordStats,LeaderInfo,MarketLandscapePrompt,OpportunityFinderPrompt,TechAdoption,TechGap,TierBreakdown,} |
{CompetitorProfileData,ExecutiveBriefData,LandscapeReportData,ReportWriter,} | report_writer::{CompetitorProfileData,ExecutiveBriefData,LandscapeReportData,ReportWriter,} |
{CompetitorWeakness,ContentGap,KeywordGap,OpportunityFinderPrompt,TechGap,} | opportunity_finder::{CompetitorWeakness,ContentGap,KeywordGap,OpportunityFinderPrompt,TechGap,} |
{KeywordStats,LeaderInfo,MarketLandscapePrompt,TechAdoption,TierBreakdown,} | market_landscape::{KeywordStats,LeaderInfo,MarketLandscapePrompt,TechAdoption,TierBreakdown,} |
{ReportError,Result} | error::{ReportError,Result} |
Boundary
Reaches into infrastructure.
Shares tier domain with 41 other crates: domain-agreements, domain-billing, domain-catalog, domain-classify, domain-comments, domain-competitive-intel, domain-contact, domain-conversation-engine, … (41 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | domain |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/domain/ai-report |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_domain["domain"] --> n_infrastructure["infrastructure"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `infrastructure-ai` | infrastructure | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive | no | always |
thiserror | ^2 | — | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full, macros, rt-multi-thread | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["domain-ai-report"] SELF -->|runtime| n_infrastructure_ai["infrastructure-ai"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | domain_ai_report | `src/lib.rs` |
Error model
| Error type | Named by |
|---|---|
ReportError | Result |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | none detected |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 25 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 1 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
context | 1 | 0 | 0 |
cost | 1 | 0 | 0 |
error | 2 | 0 | 0 |
prompts::competitor_analysis | 1 | 0 | 0 |
prompts::executive_summary | 1 | 0 | 0 |
prompts::market_landscape | 5 | 0 | 0 |
prompts::opportunity_finder | 5 | 0 | 0 |
report_writer | 4 | 0 | 0 |
What the tests establish, by name:
system_prompt_has_no_hardcoded_vertical_content—src/context.rssystem_prompt_keeps_the_generic_guidance_verbatim—src/context.rssystem_prompt_uses_caller_supplied_business_type_and_geography—src/context.rscustom_rates_are_used_not_the_default—src/cost.rsdefault_rates_match_ported_source_constants—src/cost.rsestimate_matches_source_arithmetic—src/cost.rsestimate_zero_usage_is_zero_cost—src/cost.rsrender_handles_missing_desktop_score—src/prompts/competitor_analysis.rsrender_includes_profile_data—src/prompts/competitor_analysis.rsrender_uses_caller_supplied_business_type_not_plumbing—src/prompts/competitor_analysis.rsrender_includes_market_data—src/prompts/executive_summary.rsrender_reports_total_pages_analyzed_field—src/prompts/executive_summary.rsrender_uses_caller_supplied_business_type_not_plumbing—src/prompts/executive_summary.rsrender_computes_tech_adoption_percentages—src/prompts/market_landscape.rsrender_has_no_hardcoded_vertical_or_geography—src/prompts/market_landscape.rsrender_includes_leaders_and_keywords—src/prompts/market_landscape.rsrender_includes_gap_tables_and_weaknesses—src/prompts/opportunity_finder.rsrender_uses_caller_supplied_business_type_not_plumbing—src/prompts/opportunity_finder.rscompetitor_profile_uses_context_product_name—src/report_writer.rsexecutive_brief_propagates_provider_error—src/report_writer.rsexecutive_brief_uses_context_not_hardcoded_phoenix_plumbing_literal—src/report_writer.rsexecutive_brief_with_different_context_reflects_it—src/report_writer.rslandscape_report_uses_context_and_no_hardcoded_vertical—src/report_writer.rsprovider_name_and_availability_delegate_to_provider—src/report_writer.rstokens_and_cost_accumulate_across_calls—src/report_writer.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 29 | 37 |
Public modules with a //! block | 9 | 9 |
pie showData
title Public items with rustdoc
"Documented" : 29
"No rustdoc detected" : 8
Metrics
| Metric | Value |
|---|---|
| Rust source files | 10 |
| Source lines | 1507 |
| Code lines | 1104 |
| Public API items | 37 |
| Public modules | 9 |
| Tests | 25 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 4 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"enum" : 1
"method" : 17
"struct" : 18
"type alias" : 1
pie showData
title Rust source composition
"Code" : 1104
"Blank or comment" : 403
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.