403 lines
10 KiB
Markdown
403 lines
10 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
cp bicorder_model.json ../path/to/bicorder/public/
|
|
```
|
|
|
|
### 2. Install Classifier
|
|
|
|
Copy the JavaScript module to your source directory:
|
|
|
|
```bash
|
|
cp bicorder-classifier.js ../path/to/bicorder/src/lib/
|
|
cp bicorder-classifier.d.ts ../path/to/bicorder/src/lib/
|
|
```
|
|
|
|
### 3. Basic Usage
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
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
|
|
|
|
### Recommended Flow
|
|
|
|
```
|
|
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:**
|
|
```javascript
|
|
{
|
|
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:**
|
|
```javascript
|
|
{
|
|
ready: boolean,
|
|
keyDimensionsProvided: number,
|
|
keyDimensionsTotal: 8,
|
|
coverage: 0-100,
|
|
missingKeyDimensions: string[]
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
Test the classifier with example protocols:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```bash
|
|
node bicorder-classifier.js
|
|
```
|