← All tasks
cppclaude-code/cpp-t1 #9Not a task: already works

Time Series Trend Detector (cpp, written by Claude Code)

envgap__claude-code__cpp-t1-9

Written by a coding agent; not on GitHubWritten 2026-02-27

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/cpp-t1 #9 · read the task the agent was given
Claude Code wrote this cpp project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Time Series Trend Detector

Write a program that analyzes time series data to detect trends, seasonal patterns, and anomalies using statistical methods, and produces a visual summary report.

FUNCTIONAL REQUIREMENTS:
- Accept a CSV file path as a command-line argument with columns for timestamp and one or more numeric value columns
- Parse timestamps in multiple formats (ISO 8601, Unix epoch, and common date formats like MM/DD/YYYY, YYYY-MM-DD HH:MM:SS)
- Compute a moving average with a configurable window size via --window flag (default: 7 data points)
- Detect overall trend direction (increasing, decreasing, stable) using linear regression and report the slope and R-squared value
- Detect seasonality by computing autocorrelation at various lags and reporting the dominant period if one exists
- Identify anomalies: data points that deviate more than a configurable number of standard deviations from the moving average (--threshold flag, default: 2.0)
- Support multiple value columns: analyze each independently and report results for all
- Generate a summary report with: trend direction and strength, seasonal period (if any), count and list of anomalies with their timestamps and values, basic statistics (min, max, mean, variance)
- Save the report as a JSON file with --output flag (default: trend_report.json)
- Export the processed data (original values, moving average, anomaly flags) as a CSV file via --export flag
- If no input file is given, generate a sample time series dataset with 365 daily data points containing a linear trend, weekly seasonality, and injected anomalies, then analyze it
- Handle missing timestamps and gaps in the series by interpolating or flagging them

Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(TrendDetector VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)

# Eigen 3.4.0
FetchContent_Declare(
    eigen
    GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
    GIT_TAG 3.4.0
)
FetchContent_MakeAvailable(eigen)

# nlohmann/json 3.11.3
FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(nlohmann_json)

# csv-parser 2.3.0
FetchContent_Declare(
    csv_parser
    GIT_REPOSITORY https://github.com/vincentlaucsb/csv-parser.git
    GIT_TAG 2.3.0
)
FetchContent_MakeAvailable(csv_parser)

add_executable(detector detector.cpp)

target_link_libraries(detector
    PRIVATE
    Eigen3::Eigen
    nlohmann_json::nlohmann_json
    csv
)
detector.cpp
/**
 * Time Series Trend Detector - C++ Trial 1
 * Uses Eigen + nlohmann/json + csv-parser for time series analysis.
 */

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <chrono>
#include <iomanip>
#include <random>
#include <map>
#include <ctime>

#include <Eigen/Dense>
#include <nlohmann/json.hpp>
#include <csv.hpp>

using json = nlohmann::json;
using namespace Eigen;

// --- Data Structures ---

struct TimeSeriesPoint {
    std::string timestamp;
    double value;
    long dayIndex; // days since epoch for calculations
};

struct SummaryStats {
    int count;
    double mean, stddev, minVal, maxVal, median, q25, q75, skewness, kurtosis;
    std::string startDate, endDate;
    long durationDays;
};

struct TrendResult {
    double linearSlope, linearIntercept, linearRSquared;
    double poly2RSquared, poly3RSquared;
    int bestPolynomialDegree;
    double bestRSquared;
    std::string direction;
};

struct SeasonalResult {
    int period;
    double seasonalStrength, seasonalAmplitude, residualStd, trendComponentMean;
};

struct Anomaly {
    int index;
    std::string timestamp;
    double value;
    std::string method;
    double score;
};

// --- Utility ---

long parseDateToDays(const std::string& dateStr) {
    std::tm tm = {};
    std::istringstream ss(dateStr.substr(0, 10));
    ss >> std::get_time(&tm, "%Y-%m-%d");
    if (ss.fail()) {
        // Try alternative format
        ss.clear();
        ss.str(dateStr.substr(0, 10));
        ss >> std::get_time(&tm, "%m/%d/%Y");
    }
    auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
    auto days = std::chrono::duration_cast<std::chrono::hours>(tp.time_since_epoch()).count() / 24;
    return days;
}

double percentile(std::vector<double> data, double p) {
    std::sort(data.begin(), data.end());
    double idx = p / 100.0 * (data.size() - 1);
    int lo = static_cast<int>(std::floor(idx));
    int hi = static_cast<int>(std::ceil(idx));
    if (lo == hi) return data[lo];
    return data[lo] + (idx - lo) * (data[hi] - data[lo]);
}

// --- Sample Data Generation ---

void generateSampleData(const std::string& outputPath) {
    std::mt19937 rng(42);
    std::normal_distribution<double> noise(0, 3);
    std::uniform_real_distribution<double> uniform(0, 1);

    int nPoints = 730;
    std::vector<int> dropIndices = {50, 51, 200, 201, 202, 450};
    std::vector<int> anomalyIndices = {100, 250, 400, 550, 680};

    std::ofstream file(outputPath);
    file << "timestamp,value\n";

    int written = 0;
    for (int i = 0; i < nPoints; i++) {
        if (std::find(dropIndices.begin(), dropIndices.end(), i) != dropIndices.end())
            continue;

        double trend = 10.0 + (40.0 * i / (nPoints - 1));
        double seasonal = 15.0 * std::sin(2 * M_PI * i / 365.25);
        double weekly = 5.0 * std::sin(2 * M_PI * i / 7.0);
        double n = noise(rng);
        double value = trend + seasonal + weekly + n;

        if (std::find(anomalyIndices.begin(), anomalyIndices.end(), i) != anomalyIndices.end()) {
            value += (uniform(rng) > 0.5 ? 1 : -1) * (30 + uniform(rng) * 20);
        }

        // Calculate date
        int year = 2022, month = 1, day = 1;
        std::tm start_tm = {0, 0, 0, day, month - 1, year - 1900};
        auto start_time = std::mktime(&start_tm);
        auto date_time = start_time + i * 86400;
        std::tm* date_tm = std::localtime(&date_time);

        file << std::put_time(date_tm, "%Y-%m-%d") << ","
             << std::fixed << std::setprecision(4) << value << "\n";
        written++;
    }
    file.close();
    std::cout << "Generated sample data with " << written << " points -> " << outputPath << std::endl;
}

// --- Data Loading ---

std::vector<TimeSeriesPoint> loadData(const std::string& filepath) {
    std::vector<TimeSeriesPoint> data;

    csv::CSVReader reader(filepath);
    std::vector<std::string> colNames = reader.get_col_names();

    std::string tsCol, valCol;
    for (const auto& col : colNames) {
        std::string lower = col;
        std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
        if (lower == "timestamp" || lower == "date" || lower == "datetime") tsCol = col;
        else if (lower == "value" || lower == "val" || lower == "y") valCol = col;
    }
    if (tsCol.empty()) tsCol = colNames[0];
    if (valCol.empty()) valCol = colNames[1];

    for (auto& row : reader) {
        try {
            std::string ts = row[tsCol].get<>();
            double val = row[valCol].get<double>();
            long dayIdx = parseDateToDays(ts);
            data.push_back({ts, val, dayIdx});
        } catch (...) {
            continue;
        }
    }

    std::sort(data.begin(), data.end(), [](const auto& a, const auto& b) {
        return a.dayIndex < b.dayIndex;
    });

    return data;
}

// --- Handle Missing Timestamps ---

std::vector<TimeSeriesPoint> handleMissingTimestamps(std::vector<TimeSeriesPoint>& data) {
    if (data.size() < 2) return data;

    std::vector<long> diffs;
    for (size_t i = 1; i < data.size(); i++) {
        diffs.push_back(data[i].dayIndex - data[i - 1].dayIndex);
    }
    std::sort(diffs.begin(), diffs.end());
    long medianDiff = diffs[diffs.size() / 2];
    if (medianDiff < 1) medianDiff = 1;

    std::map<long, double> valueMap;
    for (const auto& p : data) {
        valueMap[p.dayIndex] = p.value;
    }

    std::vector<TimeSeriesPoint> filled;
    long current = data.front().dayIndex;
    long end = data.back().dayIndex;
    int filledCount = 0;

    while (current <= end) {
        if (valueMap.count(current)) {
            // Find the original timestamp string
            std::string ts;
            for (const auto& p : data) {
                if (p.dayIndex == current) { ts = p.timestamp; break; }
            }
            filled.push_back({ts, valueMap[current], current});
        } else {
            // Interpolate
            const TimeSeriesPoint* prev = nullptr;
            const TimeSeriesPoint* next = nullptr;
            for (const auto& p : data) {
                if (p.dayIndex <= current) prev = &p;
                if (p.dayIndex > current && !next) next = &p;
            }
            if (prev && next) {
                long totalDays = next->dayIndex - prev->dayIndex;
                long elapsed = current - prev->dayIndex;
                double ratio = totalDays > 0 ? static_cast<double>(elapsed) / totalDays : 0;
                double interpolated = prev->value + ratio * (next->value - prev->value);
                filled.push_back({"interpolated", interpolated, current});
                filledCount++;
            }
        }
        current += medianDiff;
    }

    if (filledCount > 0) {
        std::cout << "Filled " << filledCount << " missing timestamps via interpolation." << std::endl;
    }
    return filled;
}

// --- Summary Statistics ---

SummaryStats computeSummaryStats(const std::vector<TimeSeriesPoint>& data) {
    std::vector<double> values;
    for (const auto& p : data) values.push_back(p.value);

    int n = values.size();
    double mean = std::accumulate(values.begin(), values.end(), 0.0) / n;
    double sq_sum = 0;
    for (double v : values) sq_sum += (v - mean) * (v - mean);
    double stddev = std::sqrt(sq_sum / (n - 1));

    std::vector<double> sorted = values;
    std::sort(sorted.begin(), sorted.end());

    double skew = 0, kurt = 0;
    if (stddev > 0) {
        for (double v : values) {
            double z = (v - mean) / stddev;
            skew += z * z * z;
            kurt += z * z * z * z;
        }
        skew /= n;
        kurt = kurt / n - 3;
    }

    SummaryStats stats;
    stats.count = n;
    stats.mean = mean;
    stats.stddev = stddev;
    stats.minVal = sorted.front();
    stats.maxVal = sorted.back();
    stats.median = percentile(values, 50);
    stats.q25 = percentile(values, 25);
    stats.q75 = percentile(values, 75);
    stats.skewness = skew;
    stats.kurtosis = kurt;
    stats.startDate = data.front().timestamp;
    stats.endDate = data.back().timestamp;
    stats.durationDays = data.back().dayIndex - data.front().dayIndex;

    return stats;
}

// --- Trend Detection (using Eigen) ---

TrendResult detectTrend(const std::vector<TimeSeriesPoint>& data) {
    int n = data.size();
    VectorXd y(n);
    for (int i = 0; i < n; i++) y(i) = data[i].value;

    double yMean = y.mean();
    double ssTot = (y.array() - yMean).square().sum();

    // Linear fit: y = a + b*x
    MatrixXd X1(n, 2);
    for (int i = 0; i < n; i++) {
        X1(i, 0) = 1.0;
        X1(i, 1) = static_cast<double>(i);
    }
    VectorXd coeffs1 = (X1.transpose() * X1).ldlt().solve(X1.transpose() * y);
    VectorXd residuals1 = y - X1 * coeffs1;
    double ssRes1 = residuals1.squaredNorm();
    double r2Lin = ssTot > 0 ? 1.0 - ssRes1 / ssTot : 0;

    // Polynomial degree 2
    MatrixXd X2(n, 3);
    for (int i = 0; i < n; i++) {
        double xi = static_cast<double>(i);
        X2(i, 0) = 1.0;
        X2(i, 1) = xi;
        X2(i, 2) = xi * xi;
    }
    VectorXd coeffs2 = (X2.transpose() * X2).ldlt().solve(X2.transpose() * y);
    double ssRes2 = (y - X2 * coeffs2).squaredNorm();
    double r2Poly2 = ssTot > 0 ? 1.0 - ssRes2 / ssTot : 0;

    // Polynomial degree 3
    MatrixXd X3(n, 4);
    for (int i = 0; i < n; i++) {
        double xi = static_cast<double>(i);
        X3(i, 0) = 1.0;
        X3(i, 1) = xi;
        X3(i, 2) = xi * xi;
        X3(i, 3) = xi * xi * xi;
    }
    VectorXd coeffs3 = (X3.transpose() * X3).ldlt().solve(X3.transpose() * y);
    double ssRes3 = (y - X3 * coeffs3).squaredNorm();
    double r2Poly3 = ssTot > 0 ? 1.0 - ssRes3 / ssTot : 0;

    TrendResult result;
    result.linearSlope = coeffs1(1);
    result.linearIntercept = coeffs1(0);
    result.linearRSquared = r2Lin;
    result.poly2RSquared = r2Poly2;
    result.poly3RSquared = r2Poly3;

    result.bestPolynomialDegree = 1;
    result.bestRSquared = r2Lin;
    if (r2Poly2 - r2Lin > 0.02) { result.bestPolynomialDegree = 2; result.bestRSquared = r2Poly2; }
    if (r2Poly3 - result.bestRSquared > 0.02) { result.bestPolynomialDegree = 3; result.bestRSquared = r2Poly3; }

    if (std::abs(result.linearSlope) < 1e-6) result.direction = "flat";
    else if (result.linearSlope > 0) result.direction = "upward";
    else result.direction = "downward";

    return result;
}

// --- Seasonal Decomposition ---

SeasonalResult decomposeSeasons(const std::vector<TimeSeriesPoint>& data, int period) {
    int n = data.size();
    std::vector<double> values(n);
    for (int i = 0; i < n; i++) values[i] = data[i].value;

    // Trend via centered moving average
    std::vector<double> trendComp(n, std::nan(""));
    int halfP = period / 2;
    for (int i = halfP; i < n - halfP; i++) {
        double sum = 0;
        int count = 0;
        for (int j = i - halfP; j <= i + halfP; j++) {
            sum += values[j];
            count++;
        }
        trendComp[i] = sum / count;
    }

    // Detrended
    std::vector<double> detrended(n);
    for (int i = 0; i < n; i++) {
        detrended[i] = std::isnan(trendComp[i]) ? 0 : values[i] - trendComp[i];
    }

    // Seasonal pattern
    std::vector<double> seasonalPattern(period, 0);
    std::vector<int> seasonalCount(period, 0);
    for (int i = 0; i < n; i++) {
        if (!std::isnan(trendComp[i])) {
            seasonalPattern[i % period] += detrended[i];
            seasonalCount[i % period]++;
        }
    }
    for (int i = 0; i < period; i++) {
        if (seasonalCount[i] > 0) seasonalPattern[i] /= seasonalCount[i];
    }

    // Build components
    std::vector<double> seasonal(n), residual(n);
    for (int i = 0; i < n; i++) {
        seasonal[i] = seasonalPattern[i % period];
        residual[i] = std::isnan(trendComp[i]) ? 0 : values[i] - trendComp[i] - seasonal[i];
    }

    // Seasonal strength
    std::vector<double> validResid, validSR;
    for (int i = 0; i < n; i++) {
        if (!std::isnan(trendComp[i])) {
            validResid.push_back(residual[i]);
            validSR.push_back(seasonal[i] + residual[i]);
        }
    }

    auto variance = [](const std::vector<double>& v) {
        double m = std::accumulate(v.begin(), v.end(), 0.0) / v.size();
        double s = 0;
        for (double x : v) s += (x - m) * (x - m);
        return s / v.size();
    };

    double varResid = variance(validResid);
    double varSR = variance(validSR);
    double strength = varSR > 0 ? std::max(0.0, std::min(1.0, 1.0 - varResid / varSR)) : 0;

    double ampMin = *std::min_element(seasonalPattern.begin(), seasonalPattern.end());
    double ampMax = *std::max_element(seasonalPattern.begin(), seasonalPattern.end());

    double trendMean = 0;
    int trendCount = 0;
    for (double t : trendComp) {
        if (!std::isnan(t)) { trendMean += t; trendCount++; }
    }
    trendMean = trendCount > 0 ? trendMean / trendCount : 0;

    double residMean = std::accumulate(validResid.begin(), validResid.end(), 0.0) / validResid.size();
    double residVar = 0;
    for (double r : validResid) residVar += (r - residMean) * (r - residMean);
    double residStd = std::sqrt(residVar / (validResid.size() - 1));

    SeasonalResult result;
    result.period = period;
    result.seasonalStrength = strength;
    result.seasonalAmplitude = ampMax - ampMin;
    result.residualStd = residStd;
    result.trendComponentMean = trendMean;

    return result;
}

// --- Anomaly Detection ---

std::vector<Anomaly> detectAnomaliesZScore(const std::vector<TimeSeriesPoint>& data, double threshold = 3.0) {
    int n = data.size();
    std::vector<double> values(n);
    for (int i = 0; i < n; i++) values[i] = data[i].value;

    double mean = std::accumulate(values.begin(), values.end(), 0.0) / n;
    double sqsum = 0;
    for (double v : values) sqsum += (v - mean) * (v - mean);
    double stddev = std::sqrt(sqsum / (n - 1));

    std::vector<Anomaly> anomalies;
    if (stddev == 0) return anomalies;

    for (int i = 0; i < n; i++) {
        double z = std::abs((values[i] - mean) / stddev);
        if (z > threshold) {
            anomalies.push_back({i, data[i].timestamp, values[i], "z-score", z});
        }
    }
    return anomalies;
}

std::vector<Anomaly> detectAnomaliesIQR(const std::vector<TimeSeriesPoint>& data, double multiplier = 1.5) {
    std::vector<double> values;
    for (const auto& p : data) values.push_back(p.value);

    double q1 = percentile(values, 25);
    double q3 = percentile(values, 75);
    double iqr = q3 - q1;
    double lower = q1 - multiplier * iqr;
    double upper = q3 + multiplier * iqr;

    std::vector<Anomaly> anomalies;
    for (size_t i = 0; i < data.size(); i++) {
        if (data[i].value < lower || data[i].value > upper) {
            double score = data[i].value < lower ? lower - data[i].value : data[i].value - upper;
            anomalies.push_back({static_cast<int>(i), data[i].timestamp, data[i].value, "IQR", score});
        }
    }
    return anomalies;
}

// --- Moving Averages ---

std::map<std::string, std::vector<double>> computeMovingAverages(
    const std::vector<TimeSeriesPoint>& data, const std::vector<int>& windows = {7, 14, 30, 90}) {

    int n = data.size();
    std::vector<double> values(n);
    for (int i = 0; i < n; i++) values[i] = data[i].value;

    std::map<std::string, std::vector<double>> results;

    for (int w : windows) {
        if (w >= n) continue;

        // SMA
        std::vector<double> sma(n);
        for (int i = 0; i < n; i++) {
            int start = std::max(0, i - w + 1);
            double sum = 0;
            for (int j = start; j <= i; j++) sum += values[j];
            sma[i] = sum / (i - start + 1);
        }
        results["SMA_" + std::to_string(w)] = sma;

        // EMA
        std::vector<double> ema(n);
        double alpha = 2.0 / (w + 1);
        ema[0] = values[0];
        for (int i = 1; i < n; i++) {
            ema[i] = alpha * values[i] + (1 - alpha) * ema[i - 1];
        }
        results["EMA_" + std::to_string(w)] = ema;
    }

    return results;
}

// --- Console Report ---

void printConsoleReport(const SummaryStats& stats, const TrendResult& trend,
                         const SeasonalResult& seasonal,
                         const std::vector<Anomaly>& zAnomalies,
                         const std::vector<Anomaly>& iqrAnomalies) {

    std::cout << "\n" << std::string(70, '=') << "\n";
    std::cout << "       TIME SERIES TREND DETECTION REPORT\n";
    std::cout << std::string(70, '=') << "\n";

    std::cout << "\n--- Summary Statistics ---\n";
    std::cout << "  Data Points:     " << stats.count << "\n";
    std::cout << "  Date Range:      " << stats.startDate << " to " << stats.endDate << "\n";
    std::cout << "  Duration:        " << stats.durationDays << " days\n";
    std::cout << std::fixed << std::setprecision(4);
    std::cout << "  Mean:            " << stats.mean << "\n";
    std::cout << "  Std Dev:         " << stats.stddev << "\n";
    std::cout << "  Min:             " << stats.minVal << "\n";
    std::cout << "  Max:             " << stats.maxVal << "\n";
    std::cout << "  Median:          " << stats.median << "\n";
    std::cout << "  Skewness:        " << stats.skewness << "\n";
    std::cout << "  Kurtosis:        " << stats.kurtosis << "\n";

    std::cout << "\n--- Trend Analysis ---\n";
    std::cout << "  Direction:       " << trend.direction << "\n";
    std::cout << std::setprecision(6);
    std::cout << "  Linear Slope:    " << trend.linearSlope << " per time step\n";
    std::cout << std::setprecision(4);
    std::cout << "  Linear R^2:      " << trend.linearRSquared << "\n";
    std::cout << "  Poly(2) R^2:     " << trend.poly2RSquared << "\n";
    std::cout << "  Poly(3) R^2:     " << trend.poly3RSquared << "\n";
    std::cout << "  Best Fit Degree: " << trend.bestPolynomialDegree << "\n";

    std::cout << "\n--- Seasonal Decomposition ---\n";
    std::cout << "  Period:          " << seasonal.period << "\n";
    std::cout << "  Strength:        " << seasonal.seasonalStrength << "\n";
    std::cout << "  Amplitude:       " << seasonal.seasonalAmplitude << "\n";
    std::cout << "  Residual Std:    " << seasonal.residualStd << "\n";

    std::cout << "\n--- Anomaly Detection ---\n";
    std::cout << "  Z-score anomalies: " << zAnomalies.size() << "\n";
    int limit = std::min(5, static_cast<int>(zAnomalies.size()));
    for (int i = 0; i < limit; i++) {
        std::cout << std::setprecision(2);
        std::cout << "    " << zAnomalies[i].timestamp
                  << "  value=" << zAnomalies[i].value
                  << "  z=" << zAnomalies[i].score << "\n";
    }
    if (zAnomalies.size() > 5)
        std::cout << "    ... and " << (zAnomalies.size() - 5) << " more\n";

    std::cout << "  IQR anomalies:     " << iqrAnomalies.size() << "\n";
    limit = std::min(5, static_cast<int>(iqrAnomalies.size()));
    for (int i = 0; i < limit; i++) {
        std::cout << "    " << iqrAnomalies[i].timestamp
                  << "  value=" << iqrAnomalies[i].value << "\n";
    }
    if (iqrAnomalies.size() > 5)
        std::cout << "    ... and " << (iqrAnomalies.size() - 5) << " more\n";

    std::cout << "\n" << std::string(70, '=') << "\n";
}

// --- Save Report ---

void saveReport(const SummaryStats& stats, const TrendResult& trend,
                 const SeasonalResult& seasonal,
                 const std::vector<Anomaly>& zAnomalies,
                 const std::vector<Anomaly>& iqrAnomalies,
                 const std::string& outputPath) {

    json report;
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    std::ostringstream oss;
    oss << std::put_time(std::localtime(&time), "%Y-%m-%dT%H:%M:%S");
    report["generated_at"] = oss.str();

    report["summary_statistics"] = {
        {"count", stats.count}, {"mean", stats.mean}, {"std", stats.stddev},
        {"min", stats.minVal}, {"max", stats.maxVal}, {"median", stats.median},
        {"q25", stats.q25}, {"q75", stats.q75}, {"skewness", stats.skewness},
        {"kurtosis", stats.kurtosis}, {"start_date", stats.startDate},
        {"end_date", stats.endDate}, {"duration_days", stats.durationDays}
    };

    report["trend_analysis"] = {
        {"linear_slope", trend.linearSlope}, {"linear_intercept", trend.linearIntercept},
        {"linear_r_squared", trend.linearRSquared}, {"poly2_r_squared", trend.poly2RSquared},
        {"poly3_r_squared", trend.poly3RSquared}, {"best_polynomial_degree", trend.bestPolynomialDegree},
        {"best_r_squared", trend.bestRSquared}, {"direction", trend.direction}
    };

    report["seasonal_decomposition"] = {
        {"period", seasonal.period}, {"seasonal_strength", seasonal.seasonalStrength},
        {"seasonal_amplitude", seasonal.seasonalAmplitude}, {"residual_std", seasonal.residualStd},
        {"trend_component_mean", seasonal.trendComponentMean}
    };

    json zDetections = json::array();
    for (const auto& a : zAnomalies) {
        zDetections.push_back({{"index", a.index}, {"timestamp", a.timestamp},
                                {"value", a.value}, {"z_score", a.score}, {"method", a.method}});
    }
    json iqrDetections = json::array();
    for (const auto& a : iqrAnomalies) {
        iqrDetections.push_back({{"index", a.index}, {"timestamp", a.timestamp},
                                  {"value", a.value}, {"score", a.score}, {"method", a.method}});
    }

    report["anomalies"] = {
        {"z_score", {{"count", zAnomalies.size()}, {"threshold", 3.0}, {"detections", zDetections}}},
        {"iqr", {{"count", iqrAnomalies.size()}, {"multiplier", 1.5}, {"detections", iqrDetections}}}
    };

    std::ofstream file(outputPath);
    file << report.dump(2);
    file.close();
    std::cout << "Report saved to " << outputPath << std::endl;
}

// --- Main ---

int main(int argc, char* argv[]) {
    std::string inputPath;
    std::string outputDir = ".";
    double zScoreThreshold = 3.0;
    double iqrMultiplier = 1.5;
    int seasonalPeriod = -1;

    for (int i = 1; i < argc; i++) {
        std::string arg = argv[i];
        if ((arg == "--input" || arg == "-i") && i + 1 < argc) inputPath = argv[++i];
        else if ((arg == "--output" || arg == "-o") && i + 1 < argc) outputDir = argv[++i];
        else if (arg == "--zscore-threshold" && i + 1 < argc) zScoreThreshold = std::stod(argv[++i]);
        else if (arg == "--iqr-multiplier" && i + 1 < argc) iqrMultiplier = std::stod(argv[++i]);
        else if ((arg == "--period" || arg == "-p") && i + 1 < argc) seasonalPeriod = std::stoi(argv[++i]);
    }

    if (inputPath.empty()) {
        std::cout << "No input file specified. Generating sample data..." << std::endl;
        inputPath = outputDir + "/sample_timeseries.csv";
        generateSampleData(inputPath);
    }

    std::cout << "Loading data from " << inputPath << "..." << std::endl;
    auto data = loadData(inputPath);
    std::cout << "Loaded " << data.size() << " records." << std::endl;

    data = handleMissingTimestamps(data);
    std::cout << "After filling gaps: " << data.size() << " records." << std::endl;

    auto stats = computeSummaryStats(data);
    auto trend = detectTrend(data);

    if (seasonalPeriod < 0) {
        int n = data.size();
        if (n >= 730) seasonalPeriod = 365;
        else if (n >= 60) seasonalPeriod = 30;
        else if (n >= 14) seasonalPeriod = 7;
        else seasonalPeriod = std::max(2, n / 3);
    }
    if (seasonalPeriod >= static_cast<int>(data.size()) / 2) {
        seasonalPeriod = std::max(2, static_cast<int>(data.size()) / 3);
    }

    auto seasonal = decomposeSeasons(data, seasonalPeriod);
    auto zAnomalies = detectAnomaliesZScore(data, zScoreThreshold);
    auto iqrAnomalies = detectAnomaliesIQR(data, iqrMultiplier);
    auto movingAvgs = computeMovingAverages(data);

    printConsoleReport(stats, trend, seasonal, zAnomalies, iqrAnomalies);

    std::string reportPath = outputDir + "/trend_report.json";
    saveReport(stats, trend, seasonal, zAnomalies, iqrAnomalies, reportPath);

    std::cout << "\nAnalysis complete." << std::endl;
    return 0;
}
README.md
# Time Series Trend Detector - C++ Trial 1

Analyzes time series data to detect trends, seasonal patterns, and anomalies using Eigen, nlohmann/json, and csv-parser.

## Dependencies

- Eigen 3.4.0
- nlohmann/json 3.11.3
- csv-parser 2.3.0

## Build & Run

```bash
mkdir build && cd build
cmake ..
cmake --build .
./detector

# With input file
./detector --input data.csv --output results/
```

## Output

- Console report with summary statistics, trend analysis, seasonal decomposition, and anomaly detection
- `trend_report.json` - Full analysis results in JSON format