Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Review

on: [pull_request]

env:
TARGET_DRUPAL_CORE_VERSION: 11

jobs:
drupal-lint:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- name: Lint Drupal
run: |
docker compose --profile lint run drupal-lint

# TODO: Enable drupal-check after fixing pre-existing PHPStan errors
# (array type hints, return type specifications) in the codebase.
# drupal-check:
# runs-on: ubuntu-latest
# timeout-minutes: 60
# steps:
# - uses: actions/checkout@v4
# - name: Check Drupal compatibility
# run: |
# docker compose --profile lint run drupal-check
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,68 @@ navigator.sendBeacon('/modules/contrib/rl/rl.php', rewardData);
Full algorithm details available in source code:
[ThompsonCalculator.php](https://git.drupalcode.org/project/rl/-/blob/1.x/src/Service/ThompsonCalculator.php)

## Analytics API

The RL module provides an analytics service (`rl.analyzer`) for accessing experiment performance data, insights, and recommendations. This service can be used by Drush commands, other modules, or custom code.

### Drush Commands

```bash
# List all experiments
drush rl:list
drush rl:list --format=json

# Get detailed experiment status
drush rl:status ab_test_button_color --format=yaml

# Get arm performance with human-readable labels
drush rl:performance ai_sorting-help_center_categories-block_1 --limit=10

# Get historical trends
drush rl:trends ab_test_headline_variants --period=weekly --periods=8

# Full analysis with recommendations (AI-optimized)
drush rl:analyze mock_10_arm_test --format=json

# Export complete experiment data
drush rl:export my_experiment --snapshots --format=json
```

### Service API for Other Modules

```php
// Get the analyzer service
$analyzer = \Drupal::service('rl.analyzer');

// List all experiments with summary stats
$experiments = $analyzer->listExperiments();

// Get detailed status for an experiment
$status = $analyzer->getStatus('my_experiment');
// Returns: phase, confidence, value generated vs equal distribution

// Get arm performance with resolved labels
$performance = $analyzer->getPerformance('my_experiment', limit: 20);
// Returns: arms with labels, rates, vs_average, confidence

// Get historical trends
$trends = $analyzer->getTrends('my_experiment', 'weekly', 8);
// Returns: period data with trend analysis

// Full export for deep analysis
$export = $analyzer->export('my_experiment', includeSnapshots: true);
```

### AI Integration

The Drush commands are designed for AI tool consumption:
- Human-readable labels (entity IDs resolved to titles)
- Pre-computed insights (vs_average, confidence levels)
- Actionable recommendations
- JSON/YAML output formats

## Resources

- [Multi-Armed Bandit Problem](https://en.wikipedia.org/wiki/Multi-armed_bandit) - Wikipedia overview
- [Thompson Sampling Paper](https://www.jstor.org/stable/2332286) - Original research
- [Finite-time Analysis](https://homes.di.unimi.it/~cesa-bianchi/Pubblicazioni/ml-02.pdf) - Mathematical foundations
- [Finite-time Analysis](https://homes.di.unimi.it/~cesa-bianchi/Pubblicazioni/ml-02.pdf) - Mathematical foundations
32 changes: 32 additions & 0 deletions docs/rl_project_desc.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,38 @@ <h4>Drupal Routes - Full API</h4>
<li><code>GET /rl/experiment/{uuid}/scores</code> - Get scores</li>
</ul>

<h3>Analytics API &amp; Drush Commands</h3>

<p>The RL module provides an analytics service for accessing experiment insights. Use it via Drush or inject it into other modules.</p>

<h4>Drush Commands</h4>
<pre><code># List all experiments
drush rl:list --format=json

# Get detailed experiment status
drush rl:status my_experiment --format=yaml

# Get arm performance with human-readable labels
drush rl:performance my_experiment --limit=10

# Full analysis with AI-friendly recommendations
drush rl:analyze my_experiment --format=json</code></pre>

<h4>Service API for Other Modules</h4>
<pre><code>// Get the analyzer service
$analyzer = \Drupal::service('rl.analyzer');

// List experiments with summary stats
$experiments = $analyzer->listExperiments();

// Get performance with resolved entity labels
$performance = $analyzer->getPerformance('my_experiment', limit: 20);

// Get status with confidence and recommendations
$status = $analyzer->getStatus('my_experiment');</code></pre>

<p>The analytics API resolves entity IDs to human-readable labels, pre-computes insights like "vs average" comparisons, and provides actionable recommendations—ideal for AI tools and integrations.</p>

<p><strong>Ready to get started?</strong> Install the module and begin implementing intelligent, adaptive decision-making in your Drupal applications today!</p>

<h3>Resources</h3>
Expand Down
2 changes: 1 addition & 1 deletion rl.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ description: 'Core API module for tracking multi-armed bandit experiments using
core_version_requirement: ^10.3 | ^11
package: Custom
dependencies:
- drupal:system
- drupal:system
2 changes: 0 additions & 2 deletions rl.install
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* Install, update and uninstall functions for the Reinforcement Learning module.
*/

use Drupal\Core\Database\Database;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;

Expand Down Expand Up @@ -342,4 +341,3 @@ function rl_schema() {

return $schema;
}

2 changes: 1 addition & 1 deletion rl.links.menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ rl.reports.experiments:
description: 'View reinforcement learning experiments and their statistics.'
route_name: rl.reports.experiments
parent: system.admin_reports
weight: 10
weight: 10
2 changes: 1 addition & 1 deletion rl.module
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ function rl_help($route_name, RouteMatchInterface $route_match) {
$output .= '</ul>';
return $output;
}
}
}
2 changes: 1 addition & 1 deletion rl.permissions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ administer reinforcement learning:

view reinforcement learning data:
title: 'View Reinforcement Learning data'
description: 'Access experiment scores and analytics'
description: 'Access experiment scores and analytics'
36 changes: 18 additions & 18 deletions rl.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
/**
* @file
* Handles RL experiment tracking via AJAX with minimal bootstrap.
*
*
* Following the statistics.php architecture for optimal performance.
* Updated for Drupal 10/11 compatibility.
*/

use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;

// CRITICAL: Only accept POST requests for security and caching reasons
// CRITICAL: Only accept POST requests for security and caching reasons.
$action = filter_input(INPUT_POST, 'action', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$experiment_uuid = filter_input(INPUT_POST, 'experiment_uuid', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$arm_id = filter_input(INPUT_POST, 'arm_id', FILTER_SANITIZE_FULL_SPECIAL_CHARS);

// Validate inputs more strictly
// Validate inputs more strictly.
if (!$action || !$experiment_uuid || !in_array($action, ['turn', 'turns', 'reward'])) {
http_response_code(400);
exit();
Expand All @@ -28,7 +28,7 @@
exit();
}

// Catch exceptions when site is not configured or storage fails
// Catch exceptions when site is not configured or storage fails.
try {
// Assumes module in modules/contrib/rl, so three levels below root.
chdir('../../..');
Expand All @@ -40,58 +40,58 @@
$kernel->boot();
$container = $kernel->getContainer();

// Check if experiment is registered
// Check if experiment is registered.
$registry = $container->get('rl.experiment_registry');
if (!$registry->isRegistered($experiment_uuid)) {
// Silently ignore unregistered experiments like statistics module
// Silently ignore unregistered experiments like statistics module.
exit();
}

// Get the experiment data storage service
// Get the experiment data storage service.
$storage = $container->get('rl.experiment_data_storage');

// Handle the different actions
// Handle the different actions.
switch ($action) {
case 'turn':
// Validate arm_id for single turn
// Validate arm_id for single turn.
if ($arm_id && preg_match('/^[a-zA-Z0-9_-]+$/', $arm_id)) {
$storage->recordTurn($experiment_uuid, $arm_id);
}
break;

case 'turns':
// Handle multiple turns with better validation
// Handle multiple turns with better validation.
$arm_ids = filter_input(INPUT_POST, 'arm_ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if ($arm_ids) {
$arm_ids_array = explode(',', $arm_ids);
$arm_ids_array = array_map('trim', $arm_ids_array);
// Validate each arm_id

// Validate each arm_id.
$valid_arm_ids = [];
foreach ($arm_ids_array as $aid) {
if (preg_match('/^[a-zA-Z0-9_-]+$/', $aid)) {
$valid_arm_ids[] = $aid;
}
}

if (!empty($valid_arm_ids)) {
$storage->recordTurns($experiment_uuid, $valid_arm_ids);
}
}
break;

case 'reward':
// Validate arm_id for reward
// Validate arm_id for reward.
if ($arm_id && preg_match('/^[a-zA-Z0-9_-]+$/', $arm_id)) {
$storage->recordReward($experiment_uuid, $arm_id);
}
break;
}
// Send success response

// Send success response.
http_response_code(200);

}
catch (\Exception $e) {
// Do nothing if there is PDO Exception or other failure.
}
}
2 changes: 1 addition & 1 deletion rl.routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ rl.reports.experiment_detail:
_title: 'RL Experiment Detail'
requirements:
_permission: 'administer rl experiments'
experiment_uuid: '.+'
experiment_uuid: '.+'
6 changes: 5 additions & 1 deletion rl.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ services:
rl.experiment_decorator_manager:
class: Drupal\rl\Decorator\ExperimentDecoratorManager
tags:
- { name: service_collector, tag: rl_experiment_decorator, call: addDecorator }
- { name: service_collector, tag: rl_experiment_decorator, call: addDecorator }

rl.analyzer:
class: Drupal\rl\Service\RlAnalyzer
arguments: ['@database', '@entity_type.manager']
8 changes: 4 additions & 4 deletions scripts/run-drupal-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ fi
cd drupal
mkdir -p web/modules/contrib/

# Symlink analyze_ai_brand_voice if not already linked
if [ ! -L "web/modules/contrib/analyze_ai_brand_voice" ]; then
ln -s /src web/modules/contrib/analyze_ai_brand_voice
# Symlink rl module if not already linked
if [ ! -L "web/modules/contrib/rl" ]; then
ln -s /src web/modules/contrib/rl
fi

# Install the statistic modules if D11 (removed from core).
Expand All @@ -35,4 +35,4 @@ fi
composer require $DRUPAL_CHECK_TOOL --dev

# Run drupal-check
./vendor/bin/drupal-check --drupal-root . -ad web/modules/contrib/analyze_ai_brand_voice
./vendor/bin/drupal-check --drupal-root . -ad web/modules/contrib/rl
3 changes: 3 additions & 0 deletions scripts/run-drupal-lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ EXIT_CODE=0
echo "---- Checking with PHPCompatibility PHP 8.3 and up ----"
phpcs --standard=PHPCompatibility \
--runtime-set testVersion 8.3- \
--runtime-set ignore_warnings_on_exit 1 \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,rl/vendor,.github,vendor \
-v \
Expand All @@ -17,6 +18,7 @@ fi

echo "---- Checking with Drupal standard... ----"
phpcs --standard=Drupal \
--runtime-set ignore_warnings_on_exit 1 \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,rl/vendor,.github,vendor \
-v \
Expand All @@ -28,6 +30,7 @@ fi

echo "---- Checking with DrupalPractice standard... ----"
phpcs --standard=DrupalPractice \
--runtime-set ignore_warnings_on_exit 1 \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,rl/vendor,.github,vendor \
-v \
Expand Down
2 changes: 1 addition & 1 deletion src/Controller/ExperimentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,4 @@ public function getThompsonScores(Request $request, $experiment_uuid) {
}
}

}
}
Loading