<commit_before><?php

namespace App\Console\Commands;

use App\Article;
use App\Contracts\JournalAggregateRequest;
use App\Contracts\JournalContract;
use App\Contracts\JournalHelpers;
use App\Contracts\JournalListRequest;
use App\Conversion;
use App\Model\ConversionCommerceEvent;
use App\Model\ConversionCommerceEventProduct;
use App\Model\ConversionGeneralEvent;
use App\Model\ConversionPageviewEvent;
use Carbon\Carbon;
use Illuminate\Console\Command;

class AggregateConversionEvents extends Command
{
    const COMMAND = 'conversions:aggregate-events';
    const DAYS_IN_PAST = 14;

    protected $signature = self::COMMAND . ' {--conversion_id=} {--days=' . self::DAYS_IN_PAST . '}';

    protected $description = 'Aggregate events prior given conversion';

    private $journal;

    private $journalHelper;

    public function __construct(JournalContract $journal)
    {
        parent::__construct();
        $this->journal = $journal;
        $this->journalHelper = new JournalHelpers($journal);
    }

    public function handle()
    {
        $this->line('Aggregation of conversion events started');
        $conversionId = $this->option('conversion_id') ?? null;

        $days = (int) $this->option('days');

        if ($conversionId) {
            $conversion = Conversion::find($conversionId);
            if (!$conversion) {
                $this->error("Conversion with ID $conversionId not found.");
                return;
            }

            if ($conversion->pageviewEvents->count() > 0 ||
                $conversion->commerceEvents->count() > 0 ||
                $conversion->generalEvents->count() > 0) {
                $this->info("Conversion with ID $conversionId already aggregated.");
                return;
            }

            $this->aggregateConversion($conversion, $days);
        } else {
            foreach ($this->getUnaggregatedConversions() as $conversion) {
                $this->aggregateConversion($conversion, $days);
            }
        }

        $this->line(' <info>OK!</info>');
    }

    protected function getBrowsersForUser(Conversion $conversion, $category, $action = null)
    {
        $before = $conversion->paid_at;
        // take maximum one year old browser IDs
        $after = (clone $before)->subYear();

        $browserIds = [];
        $records = $this->journal->count(JournalAggregateRequest::from($category, $action)
            ->addGroup('browser_id')
            ->addFilter('user_id', $conversion->user_id)
            ->setTime($after, $before));
        foreach ($records as $record) {
            if ($record->tags->browser_id && $record->tags->browser_id !== '') {
                $browserIds[] = $record->tags->browser_id;
            }
        }

        return $browserIds;
    }

    private function getUnaggregatedConversions()
    {
        $unionQuery = ConversionGeneralEvent::select('conversion_id')
            ->groupBy('conversion_id')
            ->union(ConversionCommerceEvent::select('conversion_id')->groupBy('conversion_id'))
            ->union(ConversionPageviewEvent::select('conversion_id')->groupBy('conversion_id'));

        return Conversion::select('conversions.*')
            ->leftJoinSub($unionQuery, 't', function ($join) {
                $join->on('conversions.id', 't.conversion_id');
            })
            ->whereNotNull('conversions.user_id')
            ->whereNull('t.conversion_id')
            ->get();
    }

    protected function aggregateConversion(Conversion $conversion, int $days)
    {
        if (!$conversion->user_id) {
            $this->line("Conversion #{$conversion->user_id} has no assigned user.");
            return;
        }

        $this->loadAndStorePageviewEvents(
            $conversion,
            $this->getBrowsersForUser($conversion, 'pageviews', 'load'),
            $days
        );

        $this->loadAndStoreCommerceEvents(
            $conversion,
            $this->getBrowsersForUser($conversion, 'commerce'),
            $days
        );
        $this->loadAndStoreGeneralEvents(
            $conversion,
            $this->getBrowsersForUser($conversion, 'events'),
            $days
        );
    }

    protected function loadAndStorePageviewEvents(Conversion $conversion, array $browserIds, $days)
    {
        if (!$browserIds) {
            return;
        }

        $from = (clone $conversion->paid_at)->subDays($days);
        $to = $conversion->paid_at;

        $r = JournalListRequest::from('pageviews')
            ->setTime($from, $to)
            ->addFilter('browser_id', ...$browserIds)
            ->setLoadTimespent();

        $events = $this->journal->list($r);

        if ($events->isNotEmpty()) {
            foreach ($events[0]->commerces as $item) {
                if (!isset($item->article->id)) {
                    continue;
                }
                $article = Article::where('external_id', $item->article->id)->first();

                if ($article) {
                    ConversionPageviewEvent::create([
                        'conversion_id' => $conversion->id,<commit_msg>Minor fix<commit_after><?php

namespace App\Console\Commands;

use App\Article;
use App\Contracts\JournalAggregateRequest;
use App\Contracts\JournalContract;
use App\Contracts\JournalHelpers;
use App\Contracts\JournalListRequest;
use App\Conversion;
use App\Model\ConversionCommerceEvent;
use App\Model\ConversionCommerceEventProduct;
use App\Model\ConversionGeneralEvent;
use App\Model\ConversionPageviewEvent;
use Carbon\Carbon;
use Illuminate\Console\Command;

class AggregateConversionEvents extends Command
{
    const COMMAND = 'conversions:aggregate-events';
    const DAYS_IN_PAST = 14;

    protected $signature = self::COMMAND . ' {--conversion_id=} {--days=' . self::DAYS_IN_PAST . '}';

    protected $description = 'Aggregate events prior given conversion';

    private $journal;

    private $journalHelper;

    public function __construct(JournalContract $journal)
    {
        parent::__construct();
        $this->journal = $journal;
        $this->journalHelper = new JournalHelpers($journal);
    }

    public function handle()
    {
        $this->line('Aggregation of conversion events started');
        $conversionId = $this->option('conversion_id') ?? null;

        $days = (int) $this->option('days');

        if ($conversionId) {
            $conversion = Conversion::find($conversionId);
            if (!$conversion) {
                $this->error("Conversion with ID $conversionId not found.");
                return;
            }

            if ($conversion->pageviewEvents->count() > 0 ||
                $conversion->commerceEvents->count() > 0 ||
                $conversion->generalEvents->count() > 0) {
                $this->info("Conversion with ID $conversionId already aggregated.");
                return;
            }

            $this->aggregateConversion($conversion, $days);
        } else {
            foreach ($this->getUnaggregatedConversions() as $conversion) {
                $this->aggregateConversion($conversion, $days);
            }
        }

        $this->line(' <info>OK!</info>');
    }

    protected function getBrowsersForUser(Conversion $conversion, $category, $action = null)
    {
        $before = $conversion->paid_at;
        // take maximum one year old browser IDs
        $after = (clone $before)->subYear();

        $browserIds = [];
        $records = $this->journal->count(JournalAggregateRequest::from($category, $action)
            ->addGroup('browser_id')
            ->addFilter('user_id', $conversion->user_id)
            ->setTime($after, $before));
        foreach ($records as $record) {
            if ($record->tags->browser_id && $record->tags->browser_id !== '') {
                $browserIds[] = $record->tags->browser_id;
            }
        }

        return $browserIds;
    }

    private function getUnaggregatedConversions()
    {
        $unionQuery = ConversionGeneralEvent::select('conversion_id')
            ->groupBy('conversion_id')
            ->union(ConversionCommerceEvent::select('conversion_id')->groupBy('conversion_id'))
            ->union(ConversionPageviewEvent::select('conversion_id')->groupBy('conversion_id'));

        return Conversion::select('conversions.*')
            ->leftJoinSub($unionQuery, 't', function ($join) {
                $join->on('conversions.id', 't.conversion_id');
            })
            ->whereNotNull('conversions.user_id')
            ->whereNull('t.conversion_id')
            ->get();
    }

    protected function aggregateConversion(Conversion $conversion, int $days)
    {
        if (!$conversion->user_id) {
            $this->line("Conversion #{$conversion->user_id} has no assigned user.");
            return;
        }

        $this->loadAndStorePageviewEvents(
            $conversion,
            $this->getBrowsersForUser($conversion, 'pageviews', 'load'),
            $days
        );

        $this->loadAndStoreCommerceEvents(
            $conversion,
            $this->getBrowsersForUser($conversion, 'commerce'),
            $days
        );
        $this->loadAndStoreGeneralEvents(
            $conversion,
            $this->getBrowsersForUser($conversion, 'events'),
            $days
        );
    }

    protected function loadAndStorePageviewEvents(Conversion $conversion, array $browserIds, $days)
    {
        if (!$browserIds) {
            return;
        }

        $from = (clone $conversion->paid_at)->subDays($days);
        $to = $conversion->paid_at;

        $r = JournalListRequest::from('pageviews')
            ->setTime($from, $to)
            ->addFilter('browser_id', ...$browserIds)
            ->setLoadTimespent();

        $events = $this->journal->list($r);

        if ($events->isNotEmpty()) {
            foreach ($events[0]->pageviews as $item) {
                if (!isset($item->article->id)) {
                    continue;
                }
                $article = Article::where('external_id', $item->article->id)->first();

                if ($article) {
                    ConversionPageviewEvent::create([
                        'conversion_id' => $conversion->id,
{'fraction_non_alphanumeric': 0.13893787967862042, 'fraction_numerical': 0.0011757789535567313, 'mean_word_length': 2.2904577691811734, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 147, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5523096', 'n_tokens_mistral': 2870, 'n_tokens_neox': 2697, 'n_words': 492}