diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index a201e06..a811724 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3829,4 +3829,13 @@ target_link_libraries(step549_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step550_test tests/step550_test.cpp) +target_include_directories(step550_test PRIVATE src) +target_link_libraries(step550_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/WorkerEfficiencyDashboardModel.h b/editor/src/WorkerEfficiencyDashboardModel.h new file mode 100644 index 0000000..90ce670 --- /dev/null +++ b/editor/src/WorkerEfficiencyDashboardModel.h @@ -0,0 +1,71 @@ +#pragma once +// Step 550: Worker Efficiency Dashboard Data Model + +#include +#include +#include + +struct WorkerRunRecord { + std::string workerId; + std::string taskClass; + bool success = false; + int tokensUsed = 0; + int latencyMs = 0; + int rejectionCount = 0; +}; + +struct WorkerTaskMetrics { + int runs = 0; + int successCount = 0; + int failureCount = 0; + int totalTokens = 0; + int totalLatencyMs = 0; + int totalRejections = 0; + double successRate = 0.0; + double avgTokens = 0.0; + double avgLatencyMs = 0.0; + double avgRejections = 0.0; +}; + +struct WorkerEfficiencySnapshot { + std::map byWorker; + std::map byTaskClass; +}; + +class WorkerEfficiencyDashboardModel { +public: + void record(const WorkerRunRecord& r) { records_.push_back(r); } + + WorkerEfficiencySnapshot snapshot() const { + WorkerEfficiencySnapshot out; + for (const auto& r : records_) { + accumulate(out.byWorker[r.workerId], r); + accumulate(out.byTaskClass[r.taskClass], r); + } + finalize(out.byWorker); + finalize(out.byTaskClass); + return out; + } + +private: + std::vector records_; + + static void accumulate(WorkerTaskMetrics& m, const WorkerRunRecord& r) { + ++m.runs; + if (r.success) ++m.successCount; else ++m.failureCount; + m.totalTokens += r.tokensUsed; + m.totalLatencyMs += r.latencyMs; + m.totalRejections += r.rejectionCount; + } + + static void finalize(std::map& table) { + for (auto& kv : table) { + auto& m = kv.second; + if (m.runs == 0) continue; + m.successRate = static_cast(m.successCount) / static_cast(m.runs); + m.avgTokens = static_cast(m.totalTokens) / static_cast(m.runs); + m.avgLatencyMs = static_cast(m.totalLatencyMs) / static_cast(m.runs); + m.avgRejections = static_cast(m.totalRejections) / static_cast(m.runs); + } + } +}; diff --git a/editor/tests/step550_test.cpp b/editor/tests/step550_test.cpp new file mode 100644 index 0000000..8e7a038 --- /dev/null +++ b/editor/tests/step550_test.cpp @@ -0,0 +1,170 @@ +// Step 550: Worker Efficiency Dashboard Data Model (12 tests) + +#include "WorkerEfficiencyDashboardModel.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static WorkerRunRecord run(const std::string& worker, + const std::string& task, + bool success, + int tokens, + int latency, + int rejects) { + WorkerRunRecord r; + r.workerId = worker; + r.taskClass = task; + r.success = success; + r.tokensUsed = tokens; + r.latencyMs = latency; + r.rejectionCount = rejects; + return r; +} + +void test_empty_snapshot_has_no_groups() { + TEST(empty_snapshot_has_no_groups); + WorkerEfficiencyDashboardModel m; + auto s = m.snapshot(); + CHECK(s.byWorker.empty(), "no worker groups expected"); + CHECK(s.byTaskClass.empty(), "no task groups expected"); + PASS(); +} + +void test_single_run_aggregates_worker_counts() { + TEST(single_run_aggregates_worker_counts); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 100, 0)); + auto s = m.snapshot(); + CHECK(s.byWorker["w1"].runs == 1, "worker run count mismatch"); + CHECK(s.byWorker["w1"].successCount == 1, "worker success count mismatch"); + PASS(); +} + +void test_single_run_aggregates_task_counts() { + TEST(single_run_aggregates_task_counts); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 100, 0)); + auto s = m.snapshot(); + CHECK(s.byTaskClass["rename"].runs == 1, "task run count mismatch"); + PASS(); +} + +void test_success_and_failure_counts() { + TEST(success_and_failure_counts); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 100, 0)); + m.record(run("w1", "rename", false, 25, 120, 1)); + auto s = m.snapshot(); + CHECK(s.byWorker["w1"].successCount == 1, "success count mismatch"); + CHECK(s.byWorker["w1"].failureCount == 1, "failure count mismatch"); + PASS(); +} + +void test_success_rate_computation() { + TEST(success_rate_computation); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 100, 0)); + m.record(run("w1", "rename", false, 25, 120, 1)); + auto s = m.snapshot(); + CHECK(s.byWorker["w1"].successRate > 0.49 && s.byWorker["w1"].successRate < 0.51, + "success rate should be 0.5"); + PASS(); +} + +void test_average_tokens_computation() { + TEST(average_tokens_computation); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 100, 0)); + m.record(run("w1", "rename", true, 50, 100, 0)); + auto s = m.snapshot(); + CHECK(s.byWorker["w1"].avgTokens == 40.0, "avg tokens should be 40"); + PASS(); +} + +void test_average_latency_computation() { + TEST(average_latency_computation); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 80, 0)); + m.record(run("w1", "rename", true, 30, 120, 0)); + auto s = m.snapshot(); + CHECK(s.byWorker["w1"].avgLatencyMs == 100.0, "avg latency should be 100"); + PASS(); +} + +void test_average_rejections_computation() { + TEST(average_rejections_computation); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 80, 0)); + m.record(run("w1", "rename", false, 30, 120, 2)); + auto s = m.snapshot(); + CHECK(s.byWorker["w1"].avgRejections == 1.0, "avg rejections should be 1"); + PASS(); +} + +void test_multiple_workers_are_grouped_separately() { + TEST(multiple_workers_are_grouped_separately); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 80, 0)); + m.record(run("w2", "rename", false, 30, 120, 2)); + auto s = m.snapshot(); + CHECK(s.byWorker.size() == 2, "should have two worker groups"); + PASS(); +} + +void test_multiple_task_classes_are_grouped_separately() { + TEST(multiple_task_classes_are_grouped_separately); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 80, 0)); + m.record(run("w1", "extract", true, 40, 140, 1)); + auto s = m.snapshot(); + CHECK(s.byTaskClass.size() == 2, "should have two task groups"); + PASS(); +} + +void test_totals_accumulate_per_group() { + TEST(totals_accumulate_per_group); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 80, 0)); + m.record(run("w1", "rename", true, 50, 120, 2)); + auto s = m.snapshot(); + CHECK(s.byWorker["w1"].totalTokens == 80, "total tokens mismatch"); + CHECK(s.byWorker["w1"].totalLatencyMs == 200, "total latency mismatch"); + CHECK(s.byWorker["w1"].totalRejections == 2, "total rejections mismatch"); + PASS(); +} + +void test_task_group_metrics_reflect_cross_worker_runs() { + TEST(task_group_metrics_reflect_cross_worker_runs); + WorkerEfficiencyDashboardModel m; + m.record(run("w1", "rename", true, 30, 80, 0)); + m.record(run("w2", "rename", false, 50, 140, 2)); + auto s = m.snapshot(); + CHECK(s.byTaskClass["rename"].runs == 2, "task runs mismatch"); + CHECK(s.byTaskClass["rename"].failureCount == 1, "task failures mismatch"); + PASS(); +} + +int main() { + std::cout << "Step 550: Worker Efficiency Dashboard Data Model\n"; + + test_empty_snapshot_has_no_groups(); // 1 + test_single_run_aggregates_worker_counts(); // 2 + test_single_run_aggregates_task_counts(); // 3 + test_success_and_failure_counts(); // 4 + test_success_rate_computation(); // 5 + test_average_tokens_computation(); // 6 + test_average_latency_computation(); // 7 + test_average_rejections_computation(); // 8 + test_multiple_workers_are_grouped_separately(); // 9 + test_multiple_task_classes_are_grouped_separately(); // 10 + test_totals_accumulate_per_group(); // 11 + test_task_group_metrics_reflect_cross_worker_runs(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 0e44848..33f5176 100644 --- a/progress.md +++ b/progress.md @@ -9587,3 +9587,37 @@ with priority and savings estimates. - `editor/src/CostReductionSuggestionEngine.h` within header-size limit (`68` <= `600`) - `editor/tests/step549_test.cpp` within test-file size guidance (`146` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 550: Worker Efficiency Dashboard Data Model +**Status:** PASS (12/12 tests) + +Implements a worker-efficiency dashboard aggregation model covering completion, +cost, rejection, and latency metrics by worker and task class. + +**Files added:** +- `editor/src/WorkerEfficiencyDashboardModel.h` - dashboard model module: + - records per-run worker/task telemetry + - aggregates metrics by worker and by task class + - computes success/failure counts and rates + - computes total/average tokens, latency, and rejection counts +- `editor/tests/step550_test.cpp` - 12 tests covering: + - empty snapshot behavior + - worker/task grouping behavior + - success/failure and success-rate computations + - average tokens/latency/rejections computations + - aggregate totals behavior + - cross-worker task-class aggregation behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step550_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step550_test step549_test` - PASS +- `./editor/build-native/step550_test` - PASS (12/12) +- `./editor/build-native/step549_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/WorkerEfficiencyDashboardModel.h` within header-size limit (`71` <= `600`) +- `editor/tests/step550_test.cpp` within test-file size guidance (`170` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`