target fix
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
package kz.konturai.parser.dto.targeting;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Root DTO for parsing the Targeting Orchestrator response.
|
||||
* Maps the AI output to concrete typed objects.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class TargetingOrchestrationDto {
|
||||
|
||||
@NotBlank
|
||||
private String orchestrationId;
|
||||
|
||||
@NotNull
|
||||
private PlatformTarget platformTarget;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
private List<CampaignDto> campaigns;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
private List<AutomationRuleDto> automationRules;
|
||||
|
||||
public enum PlatformTarget {
|
||||
META_ADS, GOOGLE_ADS, TIKTOK_ADS
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class CampaignDto {
|
||||
@NotBlank
|
||||
private String campaignName;
|
||||
|
||||
@NotBlank
|
||||
private String objective;
|
||||
|
||||
private List<String> specialAdCategories;
|
||||
|
||||
@NotBlank
|
||||
private String buyingType;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private BudgetStrategyDto budgetStrategy;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<AdSetDto> adsets;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class BudgetStrategyDto {
|
||||
@NotNull
|
||||
private Boolean isCbo;
|
||||
|
||||
private BigDecimal campaignDailyBudgetUsd;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class AdSetDto {
|
||||
@NotBlank
|
||||
private String adsetName;
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
private BigDecimal dailyBudgetUsd;
|
||||
|
||||
@NotBlank
|
||||
private String optimizationGoal;
|
||||
|
||||
@NotBlank
|
||||
private String billingEvent;
|
||||
|
||||
@NotBlank
|
||||
private String bidStrategy;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private TargetingDto targeting;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<AdDto> ads;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class TargetingDto {
|
||||
@NotNull
|
||||
@Valid
|
||||
private GeoLocationsDto geoLocations;
|
||||
|
||||
@NotNull
|
||||
private Integer ageMin;
|
||||
|
||||
@NotNull
|
||||
private Integer ageMax;
|
||||
|
||||
private List<Integer> genders;
|
||||
|
||||
@Valid
|
||||
private List<FlexibleSpecDto> flexibleSpec;
|
||||
|
||||
@Valid
|
||||
private ExclusionsDto exclusions;
|
||||
|
||||
private List<String> publisherPlatforms;
|
||||
|
||||
private List<String> devicePlatforms;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class GeoLocationsDto {
|
||||
private List<String> countries;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class FlexibleSpecDto {
|
||||
@Valid
|
||||
private List<InterestDto> interests;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class InterestDto {
|
||||
@NotBlank
|
||||
private String id;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class ExclusionsDto {
|
||||
@Valid
|
||||
private List<CustomAudienceDto> customAudiences;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class CustomAudienceDto {
|
||||
@NotBlank
|
||||
private String id;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class AdDto {
|
||||
@NotBlank
|
||||
private String adName;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private CreativePayloadDto creativePayload;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class CreativePayloadDto {
|
||||
@NotBlank
|
||||
private String assetId;
|
||||
|
||||
@NotBlank
|
||||
private String bodyTextId;
|
||||
|
||||
@NotBlank
|
||||
private String headlineId;
|
||||
|
||||
@NotBlank
|
||||
private String callToAction;
|
||||
|
||||
@NotBlank
|
||||
private String destinationUrl;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class AutomationRuleDto {
|
||||
@NotBlank
|
||||
private String ruleId;
|
||||
|
||||
@NotBlank
|
||||
private String entityLevel;
|
||||
|
||||
@NotNull
|
||||
private ConditionGroup conditionGroup;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<ConditionDto> conditions;
|
||||
|
||||
@NotBlank
|
||||
private String action;
|
||||
|
||||
private BigDecimal actionValue;
|
||||
}
|
||||
|
||||
public enum ConditionGroup {
|
||||
AND, OR
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class ConditionDto {
|
||||
@NotBlank
|
||||
private String metric;
|
||||
|
||||
@NotNull
|
||||
private Operator operator;
|
||||
|
||||
@NotNull
|
||||
private BigDecimal value;
|
||||
}
|
||||
|
||||
public enum Operator {
|
||||
EQUAL, NOT_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, IN, NOT_IN
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,202 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import kz.konturai.parser.dto.AudienceSegmentDto;
|
||||
import kz.konturai.parser.dto.BudgetOptimizationDto;
|
||||
import kz.konturai.parser.dto.TargetingCampaignRequest;
|
||||
import kz.konturai.parser.dto.targeting.TargetingOrchestrationDto;
|
||||
import kz.konturai.parser.model.MarketingAnalysisV3Document;
|
||||
import kz.konturai.parser.model.TargetingAd;
|
||||
import kz.konturai.parser.model.TargetingAdSet;
|
||||
import kz.konturai.parser.model.TargetingAudienceProfile;
|
||||
import kz.konturai.parser.model.MarketingStrategy;
|
||||
import kz.konturai.parser.model.SocialMediaCredentials;
|
||||
import kz.konturai.parser.model.TargetingCampaign;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AiTargetologistService {
|
||||
|
||||
private final AudienceSegmentationService audienceService;
|
||||
private final BudgetOptimizerService budgetOptimizerService;
|
||||
private final ABTestingService abTestingService;
|
||||
private final OpenAIAnalyticsService openAiService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final SerperSearchService serperSearchService;
|
||||
|
||||
public TargetingAudienceProfile generateAudienceProfile(String analysisId, TargetingCampaignRequest req, MarketingAnalysisV3Document analysis) {
|
||||
log.info("Generating Audience Profile for analysis {}", analysisId);
|
||||
|
||||
List<AudienceSegmentDto> aiSegments = audienceService.segmentAudience(analysis);
|
||||
|
||||
TargetingAudienceProfile overrides = req.getAudienceOverride() != null ? TargetingAudienceProfile.builder()
|
||||
.ageMin(req.getAudienceOverride().getAgeMin())
|
||||
.ageMax(req.getAudienceOverride().getAgeMax())
|
||||
.genders(req.getAudienceOverride().getGenders())
|
||||
.build() : new TargetingAudienceProfile();
|
||||
|
||||
return audienceService.mergeSegmentsWithExistingAudience(aiSegments, overrides);
|
||||
}
|
||||
|
||||
public List<TargetingAdSet> generateAdCreatives(TargetingCampaign campaign, MarketingAnalysisV3Document analysis) {
|
||||
log.info("Generating Ad Creatives for campaign {}", campaign.getId());
|
||||
List<TargetingAdSet> adSets = new ArrayList<>();
|
||||
/**
|
||||
* Executes the "Antigravity" Orchestration Pipeline.
|
||||
* Maps business metrics, strategy data, and ready-made media assets into a strictly validated JSON payload.
|
||||
*
|
||||
* @param campaign The current campaign context (budget, goals, platforms).
|
||||
* @param analysis The underlying strategy analysis (audiences, triggers, market).
|
||||
* @param strategy The content strategy housing ready-made texts and media.
|
||||
* @param credentials Ad Account credentials (to pass platform/ad-account IDs context).
|
||||
* @return Fully populated JSON Orchestration DTO.
|
||||
*/
|
||||
public TargetingOrchestrationDto orchestrateCampaign(
|
||||
TargetingCampaign campaign,
|
||||
MarketingAnalysisV3Document analysis,
|
||||
MarketingStrategy strategy,
|
||||
List<SocialMediaCredentials> credentials) {
|
||||
|
||||
log.info("[Antigravity Pipeline] Starting orchestration for campaign {}, analysisId: {}", campaign.getId(), analysis.getId());
|
||||
|
||||
try {
|
||||
String context = objectMapper.writeValueAsString(analysis.getResultData());
|
||||
String objective = campaign.getObjective() != null ? campaign.getObjective().name() : "TRAFFIC";
|
||||
String systemPrompt = buildSystemMandate();
|
||||
String userPrompt = buildOrchestratorContext(campaign, analysis, strategy, credentials);
|
||||
|
||||
campaign.getPlatforms().forEach(platform -> {
|
||||
TargetingAdSet adSet = TargetingAdSet.builder()
|
||||
.adSetId(UUID.randomUUID().toString())
|
||||
.name(platform.name() + " Main Catch")
|
||||
.platform(platform.name())
|
||||
.budget(campaign.getBudget().getDailyBudget() / campaign.getPlatforms().size())
|
||||
.bidStrategy("LOWEST_COST")
|
||||
.optimizationGoal(objective)
|
||||
.status("DRAFT")
|
||||
.build();
|
||||
// Generate through strict OpenAI call.
|
||||
// Notice: requesting max length for complex JSON tree, timeout scaled to 180s.
|
||||
log.info("[Antigravity Pipeline] Sending context to Neural API...");
|
||||
String rawJsonResponse = openAiService.generateWithInstructionWithModel(
|
||||
"", // text/context is baked into userPrompt
|
||||
userPrompt,
|
||||
"ru",
|
||||
"gpt-4o", // High capability model required for strict JSON compliance and deep logic
|
||||
systemPrompt,
|
||||
15000,
|
||||
180000L
|
||||
);
|
||||
|
||||
List<TargetingAd> ads = new ArrayList<>();
|
||||
if (campaign.getAbTestConfig() != null && campaign.getAbTestConfig().isEnabled()) {
|
||||
ads.addAll(abTestingService.generateABVariants(context, objective));
|
||||
} else {
|
||||
List<TargetingAd> defaultAds = abTestingService.generateABVariants(context, objective);
|
||||
if (!defaultAds.isEmpty()) ads.add(defaultAds.get(0));
|
||||
if (rawJsonResponse == null || rawJsonResponse.isBlank()) {
|
||||
throw new IllegalStateException("OpenAI returned empty orchestration payload");
|
||||
}
|
||||
|
||||
// Cleanup potential markdown blocks if the model hallucinates them despite system prompt
|
||||
String cleanJson = rawJsonResponse.replaceAll("(?s)^```json\\s*", "").replaceAll("(?s)\\s*```$", "").trim();
|
||||
log.debug("[Antigravity] Raw Orchestration JSON: {}", cleanJson);
|
||||
|
||||
// Robust specific mapper
|
||||
ObjectMapper strictMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
TargetingOrchestrationDto orchestration = strictMapper.readValue(cleanJson, TargetingOrchestrationDto.class);
|
||||
log.info("[Antigravity Pipeline] Successfully parsed TargetingOrchestrationDto. Orchestration ID: {}", orchestration.getOrchestrationId());
|
||||
|
||||
return orchestration;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[Antigravity Pipeline] CRITICAL FAILURE during orchestration: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("AI Targeting Orchestration failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSystemMandate() {
|
||||
return """
|
||||
SYSTEM ROLE & ARCHITECTURE MANDATE
|
||||
You are "Antigravity", an enterprise-grade AI Targeting Orchestrator and Media Buying Architect. Your sole function is to process sanitized marketing strategies and ready-made media assets, compiling them into a highly structured, production-ready payload for Ads Platform APIs (Meta Graph API, Google Ads API, TikTok Marketing API).
|
||||
|
||||
You DO NOT generate basic raw creatives from scratch if ready-made texts exist. You DO NOT alter the core strategy. You map inputs to rigid targeting parameters, establish A/B testing matrices, calculate budget pacing, and define algorithmic optimization rules.
|
||||
|
||||
OUTPUT CONTRACT:
|
||||
Your output must be EXCLUSIVELY a valid JSON object. DO NOT wrap it in markdown block quotes. DO NOT include any natural language explanation, introductory text, or concluding remarks. Just raw valid JSON.
|
||||
The JSON keys must be strictly snake_case conforming to the supplied schema structure.
|
||||
""";
|
||||
}
|
||||
|
||||
private String buildOrchestratorContext(
|
||||
TargetingCampaign campaign,
|
||||
MarketingAnalysisV3Document analysis,
|
||||
MarketingStrategy strategy,
|
||||
List<SocialMediaCredentials> credentials) throws JsonProcessingException {
|
||||
|
||||
// 1. Business Metrics Context
|
||||
Map<String, Object> businessMetrics = new HashMap<>();
|
||||
businessMetrics.put("target_objective", campaign.getObjective() != null ? campaign.getObjective().name() : "TRAFFIC");
|
||||
businessMetrics.put("campaign_daily_budget_kzt", campaign.getBudget() != null ? campaign.getBudget().getDailyBudget() : 0);
|
||||
businessMetrics.put("campaign_total_budget_kzt", campaign.getBudget() != null ? campaign.getBudget().getTotalBudget() : 0);
|
||||
businessMetrics.put("target_platforms", campaign.getPlatforms());
|
||||
|
||||
// 2. Extracted Media Assets (Texts with hashtags)
|
||||
List<MarketingStrategy.PostCalendarItem> mediaAssets = strategy.getPostCalendar() != null ? strategy.getPostCalendar() : List.of();
|
||||
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
prompt.append("INPUT CONTEXT (READ-ONLY):\n");
|
||||
prompt.append("1. \"business_metrics\": ").append(objectMapper.writeValueAsString(businessMetrics)).append("\n\n");
|
||||
prompt.append("2. \"strategy_matrix\": ").append(objectMapper.writeValueAsString(analysis.getResultData())).append("\n\n");
|
||||
prompt.append("3. \"media_assets\": ").append(objectMapper.writeValueAsString(mediaAssets)).append("\n\n");
|
||||
|
||||
prompt.append("""
|
||||
EXECUTION PROTOCOL
|
||||
|
||||
PHASE 1: CAMPAIGN TOPOLOGY & NAMING CONVENTION
|
||||
1. Enforce strict naming conventions for parsing: [Platform]_[Objective]_[Geo]_[Date]_[UniqueIdentifier].
|
||||
2. Determine Campaign Budget Optimization (CBO) vs. AdSet Budget Optimization (ABO) based on the number of audiences. Use ABO for strict A/B testing of specific segments; use CBO for scaling proven assets.
|
||||
3. Isolate variables. Never test more than one major variable (e.g., audience, creative format) per AdSet.
|
||||
|
||||
PHASE 2: TARGETING ENGINE TRANSLATION
|
||||
Map the "strategy_matrix" to precise API-level targeting parameters.
|
||||
1. Demographic Constraints: Calculate reasonable Min_Age, Max_Age based on the strategy.
|
||||
2. Detailed Targeting Matrix: Group interests logically. Select 2-4 HIGHLY relevant interests.
|
||||
3. Placements: Optimize dynamically based on objective.
|
||||
|
||||
PHASE 3: THE A/B TESTING & CREATIVE MAPPING
|
||||
1. Map "media_assets" (texts, images, video paths, hashtags) exactly into your ads. USE SPECIFICALLY THE post_text AND hashtags PROVIDED. DO NOT hallucinate new ad copy if it is provided there. Match the content theme to the targeting AdSet.
|
||||
2. Ensure every Ad has proper UTM tracking parameters appended to destination_url: utm_source={{site_source_name}}&utm_medium={{placement}}&utm_campaign={{campaign.name}}&utm_content={{ad.name}}.
|
||||
|
||||
PHASE 4: BUDGET ALLOCATION & PACING ALGORITHM
|
||||
1. Allocate daily budget into the created AdSets intelligently based on total campaign budget.
|
||||
2. Set optimization goals correctly (e.g., OFFSITE_CONVERSIONS or LINK_CLICKS).
|
||||
|
||||
PHASE 5: RULES ENGINE (MICRO-OPTIMIZATION PROTOCOL)
|
||||
Generate a strict set of logical conditions for the external cron-job/rules engine:
|
||||
1. EARLY_KILL: IF Spend > BudgetLimit AND actions < Threshold THEN Action = PAUSE.
|
||||
2. SCALE_UP: IF CPA < Target AND Frequency < 2.5 THEN Action = INCREASE_BUDGET.
|
||||
|
||||
REQUIRED JSON SCHEMA STRUCTURE:
|
||||
{
|
||||
"orchestration_id": "uuid-v4",
|
||||
"platform_target": "META_ADS",
|
||||
"campaigns": [
|
||||
{
|
||||
"campaign_name": "string",
|
||||
"objective": "string",
|
||||
"special_ad_categories": [],
|
||||
"buying_type": "AUCTION",
|
||||
"budget_strategy": { "is_cbo": false, "campaign_daily_budget_usd": 150.0 },
|
||||
"adsets": [
|
||||
{
|
||||
"adset_name": "string",
|
||||
"daily_budget_usd": 50.0,
|
||||
"optimization_goal": "string",
|
||||
"billing_event": "IMPRESSIONS",
|
||||
"bid_strategy": "LOWEST_COST_WITHOUT_CAP",
|
||||
"targeting": {
|
||||
"geo_locations": { "countries": ["KZ"] },
|
||||
"age_min": 18, "age_max": 65, "genders": [1],
|
||||
"flexible_spec": [ { "interests": [{ "id": "123", "name": "InterestName" }] } ],
|
||||
"exclusions": {},
|
||||
"publisher_platforms": ["facebook", "instagram"],
|
||||
"device_platforms": ["mobile"]
|
||||
},
|
||||
"ads": [
|
||||
{
|
||||
"ad_name": "string",
|
||||
"creative_payload": {
|
||||
"asset_id": "Use image_filename or video_filename from media_assets here",
|
||||
"body_text_id": "Use EXACT post_text from media_assets here",
|
||||
"headline_id": "Use EXACT theme from media_assets here",
|
||||
"call_to_action": "LEARN_MORE",
|
||||
"destination_url": "https://example.com/url_with_utms"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
adSet.setAds(ads);
|
||||
adSets.add(adSet);
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error generating Ad creatives", e);
|
||||
}
|
||||
],
|
||||
"automation_rules": [
|
||||
{
|
||||
"rule_id": "string",
|
||||
"entity_level": "ADSET",
|
||||
"condition_group": "AND",
|
||||
"conditions": [ {"metric": "spend", "operator": "GREATER_THAN", "value": 75.0} ],
|
||||
"action": "PAUSE", "action_value": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
return adSets;
|
||||
}
|
||||
|
||||
public BudgetOptimizationDto optimizeBudgetDistribution(TargetingCampaign campaign, MarketingAnalysisV3Document analysis) {
|
||||
return budgetOptimizerService.optimizeBudget(campaign, analysis);
|
||||
}
|
||||
|
||||
public List<AudienceSegmentDto> analyzeCompetitorAds(String businessNiche, List<String> cities) {
|
||||
log.info("Analyzing Competitor Ads for niche {}", businessNiche);
|
||||
try {
|
||||
String query = businessNiche + " реклама кейсы " + String.join(" ", cities);
|
||||
String searchResults = serperSearchService.search(query).toString();
|
||||
|
||||
// In a real scenario, feed searchResults into openAiService to parse out actionable segments
|
||||
// For now, returning a static mapped list or empty list.
|
||||
return new ArrayList<>();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to analyze competitor ads", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return prompt.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import kz.konturai.parser.dto.TargetingPerformanceDto;
|
||||
import kz.konturai.parser.exception.TargetingCampaignNotFoundException;
|
||||
import kz.konturai.parser.model.*;
|
||||
import kz.konturai.parser.repository.MarketingAnalysisV3Repository;
|
||||
import kz.konturai.parser.repository.MarketingStrategyRepository;
|
||||
import kz.konturai.parser.repository.TargetingCampaignRepository;
|
||||
import kz.konturai.parser.dto.targeting.TargetingOrchestrationDto;
|
||||
import kz.konturai.parser.enums.CampaignObjective;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -37,6 +39,7 @@ public class TargetingCampaignService {
|
||||
private final TikTokAdsService tikTokAdsService;
|
||||
private final SocialMediaCredentialsService credentialsService;
|
||||
private final TargetingInsightService insightService;
|
||||
private final MarketingStrategyRepository strategyRepository;
|
||||
|
||||
public TargetingCampaign createCampaign(TargetingCampaignRequest req, String userId) {
|
||||
if (req.getAnalysisId() == null || req.getAnalysisId().isBlank()) {
|
||||
@@ -86,7 +89,7 @@ public class TargetingCampaignService {
|
||||
|
||||
@Async("targetingExecutor")
|
||||
public void processAsync(String campaignId, TargetingCampaignRequest req, String userId) {
|
||||
log.info("Starting async processing for campaign {}", campaignId);
|
||||
log.info("[Targeting Pipeline] Starting async Antigravity processing for campaign {}", campaignId);
|
||||
TargetingCampaign campaign = repository.findById(campaignId).orElseThrow(() ->
|
||||
new TargetingCampaignNotFoundException("Campaign not found async: " + campaignId));
|
||||
|
||||
@@ -97,54 +100,76 @@ public class TargetingCampaignService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: AI Recommendations & Audience
|
||||
addStatusHistory(campaign, "GENERATING_AUDIENCE", "Analyzing market data & segmenting audience");
|
||||
TargetingAudienceProfile profile = aiService.generateAudienceProfile(campaign.getAnalysisId(), req, analysis);
|
||||
campaign.setAudience(profile);
|
||||
MarketingStrategy strategy = null;
|
||||
if (req.getStrategyId() != null) {
|
||||
strategy = strategyRepository.findById(req.getStrategyId()).orElse(null);
|
||||
} else {
|
||||
strategy = strategyRepository.findByAnalysisId(campaign.getAnalysisId()).orElse(null);
|
||||
}
|
||||
if (strategy == null) {
|
||||
failCampaign(campaign, "Marketing Strategy containing media assets not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<SocialMediaCredentials> credentials = credentialsService.getUserCredentials(userId);
|
||||
|
||||
addStatusHistory(campaign, "ORCHESTRATING", "Running Antigravity AI Orchestrator to map strategy and text creatives to ad API payload");
|
||||
|
||||
TargetingAIAnalysisResult aiResult = TargetingAIAnalysisResult.builder().build();
|
||||
campaign.setAiRecommendations(aiResult);
|
||||
// 1. Run The Master AI Orchestrator Pipeline
|
||||
TargetingOrchestrationDto payload = aiService.orchestrateCampaign(campaign, analysis, strategy, credentials);
|
||||
|
||||
// Step 2: Budget Optimization
|
||||
addStatusHistory(campaign, "OPTIMIZING_BUDGET", "AI allocating budget efficiently");
|
||||
aiResult.setBudgetOptimization(aiService.optimizeBudgetDistribution(campaign, analysis));
|
||||
// 2. Map Payload to Local Database Models
|
||||
addStatusHistory(campaign, "MAPPING", "Saving generated ad sets and creatives locally");
|
||||
List<TargetingAdSet> savedAdSets = new ArrayList<>();
|
||||
for (TargetingOrchestrationDto.CampaignDto apiCamp : payload.getCampaigns()) {
|
||||
for (TargetingOrchestrationDto.AdSetDto apiAdSet : apiCamp.getAdsets()) {
|
||||
TargetingAdSet adSet = TargetingAdSet.builder()
|
||||
.adSetId(java.util.UUID.randomUUID().toString())
|
||||
.name(apiAdSet.getAdsetName())
|
||||
.budget(apiAdSet.getDailyBudgetUsd() != null ? apiAdSet.getDailyBudgetUsd().doubleValue() : 0.0)
|
||||
.bidStrategy(apiAdSet.getBidStrategy())
|
||||
.optimizationGoal(apiAdSet.getOptimizationGoal())
|
||||
.status("DRAFT")
|
||||
.build();
|
||||
|
||||
List<TargetingAd> ads = new ArrayList<>();
|
||||
if (apiAdSet.getAds() != null) {
|
||||
for (TargetingOrchestrationDto.AdDto apiAd : apiAdSet.getAds()) {
|
||||
TargetingAd ad = TargetingAd.builder()
|
||||
.adId(java.util.UUID.randomUUID().toString())
|
||||
.name(apiAd.getAdName())
|
||||
.headline(apiAd.getCreativePayload().getHeadlineId())
|
||||
.primaryText(apiAd.getCreativePayload().getBodyTextId())
|
||||
.callToAction(apiAd.getCreativePayload().getCallToAction())
|
||||
.mediaUrl(apiAd.getCreativePayload().getAssetId())
|
||||
.build();
|
||||
ads.add(ad);
|
||||
}
|
||||
}
|
||||
adSet.setAds(ads);
|
||||
savedAdSets.add(adSet);
|
||||
}
|
||||
}
|
||||
campaign.setAdSets(savedAdSets);
|
||||
repository.save(campaign);
|
||||
|
||||
// Step 3: Competitor Ads
|
||||
addStatusHistory(campaign, "ANALYZING_COMPETITORS", "Reviewing competitor creatives");
|
||||
List<String> cities = profile.getLocations() != null ?
|
||||
profile.getLocations().stream().map(LocationTarget::getCityName).toList() : List.of("Алматы");
|
||||
aiResult.setCompetitorInsights(aiService.analyzeCompetitorAds("Ниша клиента", cities));
|
||||
|
||||
// Step 4: Ad Creatives
|
||||
if (req.isGenerateAdCreatives()) {
|
||||
addStatusHistory(campaign, "GENERATING_CREATIVES", "Composing Ad Headlines & Texts");
|
||||
campaign.setAdSets(aiService.generateAdCreatives(campaign, analysis));
|
||||
}
|
||||
|
||||
// Step 5: AB Test
|
||||
if (req.isEnableAbTesting() && campaign.getAdSets() != null && !campaign.getAdSets().isEmpty()) {
|
||||
addStatusHistory(campaign, "CONFIGURING_AB_TEST", "Setting up A/B Test environment");
|
||||
abTestingService.createABTest(campaign, campaign.getAdSets().get(0).getAdSetId());
|
||||
}
|
||||
|
||||
// Step 6: External Platform Publishing
|
||||
addStatusHistory(campaign, "PUBLISHING", "Pushing to Social Media Ads APIs");
|
||||
// 3. External Platform Publishing
|
||||
addStatusHistory(campaign, "PUBLISHING", "Pushing validated structure to Social Media Ads APIs");
|
||||
publishToPlatforms(campaign, userId);
|
||||
|
||||
// Step 7: Done
|
||||
// 4. Finalize
|
||||
if ("failed".equals(campaign.getStatus())) {
|
||||
addStatusHistory(campaign, "FAILED", "Some integrations failed or required credentials missing");
|
||||
} else {
|
||||
campaign.setStatus("active");
|
||||
addStatusHistory(campaign, "ACTIVE", "Campaign successfully processed and active");
|
||||
addStatusHistory(campaign, "ACTIVE", "Antigravity Pipeline successfully compiled and pushed the campaign");
|
||||
}
|
||||
|
||||
campaign.setUpdatedAt(LocalDateTime.now());
|
||||
repository.save(campaign);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error during async targeting pipeline", e);
|
||||
log.error("[Targeting Pipeline] Error during async targeting pipeline", e);
|
||||
failCampaign(campaign, "Pipeline error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user