Files
protocol-bicorder/analysis/INTEGRATION_GUIDE.md
2025-12-21 21:38:39 -07:00

10 KiB

Bicorder Classifier Integration Guide

Overview

This guide explains how to integrate the cluster classification system into the Bicorder web application to provide:

  1. Real-time cluster prediction as users fill out diagnostics
  2. Smart form selection (short vs. long form based on classification confidence)
  3. Visual feedback showing protocol family positioning

Design Philosophy

Version-based compatibility: The model includes a bicorder_version field. The classifier checks that versions match. When bicorder.json structure changes:

  1. Increment the version number in bicorder.json
  2. Retrain the model with python3 export_model_for_js.py
  3. The new model will have the updated version

This ensures the web app and model stay in sync without complex backward compatibility.

Files

  • bicorder_model.json - Trained model parameters (5KB, embed in app)
  • bicorder-classifier.js - JavaScript implementation
  • bicorder-classifier.d.ts - TypeScript type definitions

Quick Start

1. Copy Model File

Copy bicorder_model.json to your web app's public/static assets:

cp bicorder_model.json ../path/to/bicorder/public/

2. Install Classifier

Copy the JavaScript module to your source directory:

cp bicorder-classifier.js ../path/to/bicorder/src/lib/
cp bicorder-classifier.d.ts ../path/to/bicorder/src/lib/

3. Basic Usage

import { loadClassifier } from './lib/bicorder-classifier.js';

// Load model once at app startup
const classifier = await loadClassifier('/bicorder_model.json');

// As user fills in diagnostic form
function onDimensionChange(dimensionName, value) {
  const currentRatings = getCurrentFormValues(); // Your form state

  const result = classifier.predict(currentRatings);

  console.log(`Cluster: ${result.clusterName}`);
  console.log(`Confidence: ${result.confidence}%`);
  console.log(`Recommend: ${result.recommendedForm} form`);

  updateUI(result);
}

Integration Patterns

Pattern 1: Progressive Classification Display

Show classification results as the user fills out the form:

// React/Svelte component example
function DiagnosticForm() {
  const [ratings, setRatings] = useState({});
  const [classification, setClassification] = useState(null);

  useEffect(() => {
    if (Object.keys(ratings).length > 0) {
      const result = classifier.predict(ratings);
      setClassification(result);
    }
  }, [ratings]);

  return (
    <div>
      <DiagnosticQuestions onChange={setRatings} />

      {classification && (
        <ClassificationIndicator
          cluster={classification.clusterName}
          confidence={classification.confidence}
          completeness={classification.completeness}
        />
      )}
    </div>
  );
}

Pattern 2: Smart Form Selection

Automatically switch between short and long forms:

function DiagnosticWizard() {
  const [ratings, setRatings] = useState({});

  function handleDimensionComplete(dimension, value) {
    const newRatings = { ...ratings, [dimension]: value };
    setRatings(newRatings);

    // Check if we should switch forms
    const result = classifier.predict(newRatings);

    if (result.recommendedForm === 'long' && currentForm === 'short') {
      showFormSwitchPrompt(
        'Your protocol shows characteristics of both families. ' +
        'Would you like to use the detailed form for better classification?'
      );
    }
  }

  return <Form onDimensionComplete={handleDimensionComplete} />;
}

Pattern 3: Short Form Optimization

Only ask the 8 most discriminative dimensions for quick classification:

const shortFormDimensions = classifier.getKeyDimensions();
// Returns:
// [
//   'Design_elite_vs_vernacular',
//   'Entanglement_flocking_vs_swarming',
//   'Design_static_vs_malleable',
//   'Entanglement_obligatory_vs_voluntary',
//   'Entanglement_self-enforcing_vs_enforced',
//   'Design_explicit_vs_implicit',
//   'Entanglement_sovereign_vs_subsidiary',
//   'Design_technical_vs_social',
// ]

function ShortForm() {
  return (
    <div>
      <h2>Quick Classification (8 questions)</h2>
      {shortFormDimensions.map(dim => (
        <DimensionSlider key={dim} dimension={dim} />
      ))}
    </div>
  );
}

Pattern 4: Readiness Check

Check if user has provided enough data for reliable classification:

function ClassificationStatus() {
  const assessment = classifier.assessShortFormReadiness(ratings);

  if (!assessment.ready) {
    return (
      <div className="status-warning">
        <p>
          Need {assessment.keyDimensionsTotal - assessment.keyDimensionsProvided} more
          key dimensions for reliable classification ({assessment.coverage}% complete)
        </p>
        <ul>
          {assessment.missingKeyDimensions.slice(0, 3).map(dim => (
            <li key={dim}>{formatDimensionName(dim)}</li>
          ))}
        </ul>
      </div>
    );
  }

  return <ClassificationResult result={classifier.predict(ratings)} />;
}

UI Components

Classification Indicator

Visual indicator showing cluster and confidence:

function ClassificationIndicator({ cluster, confidence, completeness }) {
  const color = cluster === 1 ? '#2E86AB' : '#A23B72';

  return (
    <div className="classification-indicator" style={{ borderColor: color }}>
      <div className="cluster-badge" style={{ backgroundColor: color }}>
        {cluster === 1 ? 'Relational/Cultural' : 'Institutional/Bureaucratic'}
      </div>

      <div className="confidence-bar">
        <div
          className="confidence-fill"
          style={{
            width: `${confidence}%`,
            backgroundColor: color,
            opacity: 0.3 + (confidence / 100) * 0.7,
          }}
        />
        <span className="confidence-text">{confidence}% confidence</span>
      </div>

      <div className="completeness">
        {completeness}% of dimensions provided
      </div>
    </div>
  );
}

Spectrum Visualization

Show protocol position on the relational ↔ institutional spectrum:

function SpectrumVisualization({ ldaScore, distanceToBoundary }) {
  // Scale LDA score to 0-100 for display
  // Typical range is -4 to +4
  const position = ((ldaScore + 4) / 8) * 100;
  const boundaryZone = distanceToBoundary < 0.5;

  return (
    <div className="spectrum">
      <div className="spectrum-bar">
        <div className="spectrum-label left">Relational/Cultural</div>
        <div className="spectrum-label right">Institutional/Bureaucratic</div>

        <div className="spectrum-track">
          {boundaryZone && (
            <div className="boundary-zone" style={{ left: '45%', width: '10%' }}>
              Boundary
            </div>
          )}
          <div
            className="protocol-marker"
            style={{ left: `${position}%` }}
            title={`LDA Score: ${ldaScore.toFixed(2)}`}
          />
        </div>
      </div>
    </div>
  );
}

Form Selection Logic

When to Use Short Form

  • Initial protocol scan
  • User wants quick classification
  • Protocol clearly fits one family (confidence > 60%, distance > 0.5)

When to Use Long Form

  • Protocol near boundary (distance < 0.5)
  • Low confidence (< 60%)
  • User wants detailed analysis
  • Research/documentation purposes
User starts diagnostic
  ↓
Show short form (8 key dimensions)
  ↓
Calculate partial classification
  ↓
Is confidence > 60% AND completeness > 75%?
  ↓ YES                    ↓ NO
Show result          Offer long form
                     "For better accuracy,
                     complete full diagnostic?"

API Reference

predict(ratings, options)

Main classification function.

Parameters:

  • ratings: Object mapping dimension names to values (1-9)
  • options.detailed: Return detailed information (default: true)

Returns:

{
  cluster: 1 | 2,
  clusterName: "Relational/Cultural" | "Institutional/Bureaucratic",
  confidence: 0-100,
  completeness: 0-100,
  recommendedForm: "short" | "long",
  // If detailed: true
  ldaScore: number,
  distanceToBoundary: number,
  dimensionsProvided: number,
  dimensionsTotal: 23,
  keyDimensionsProvided: number,
  keyDimensionsTotal: 8
}

explainClassification(ratings)

Generate human-readable explanation.

Returns: String with explanation text

getKeyDimensions()

Get the 8 most discriminative dimensions for short form.

Returns: Array of dimension names

assessShortFormReadiness(ratings)

Check if enough key dimensions are provided.

Returns:

{
  ready: boolean,
  keyDimensionsProvided: number,
  keyDimensionsTotal: 8,
  coverage: 0-100,
  missingKeyDimensions: string[]
}

Testing

Test the classifier with example protocols:

import { BicorderClassifier } from './bicorder-classifier.js';
import modelData from './bicorder_model.json';

const classifier = new BicorderClassifier(modelData);

// Test 1: Clearly institutional
const institutional = {
  'Design_elite_vs_vernacular': 1,
  'Entanglement_obligatory_vs_voluntary': 1,
  'Entanglement_flocking_vs_swarming': 1,
};
console.log(classifier.predict(institutional));
// Expected: Cluster 2, high confidence

// Test 2: Clearly relational
const relational = {
  'Design_elite_vs_vernacular': 9,
  'Entanglement_obligatory_vs_voluntary': 9,
  'Entanglement_flocking_vs_swarming': 9,
};
console.log(classifier.predict(relational));
// Expected: Cluster 1, high confidence

// Test 3: Boundary case
const boundary = {
  'Design_elite_vs_vernacular': 5,
  'Entanglement_obligatory_vs_voluntary': 5,
};
console.log(classifier.predict(boundary));
// Expected: Recommend long form

Performance

  • Model size: ~5KB (negligible)
  • Classification time: < 1ms
  • No network calls needed (runs entirely client-side)
  • Works offline once model is loaded

Next Steps

  1. Integrate classifier into existing bicorder form
  2. Design UI components for classification display
  3. Add user preference for form selection
  4. Consider adding classification to protocol browsing/search
  5. Export classification data with completed diagnostics

Questions?

See the demo in bicorder-classifier.js for working examples, or test with:

node bicorder-classifier.js