# ProForma 2.1 Upgrade Plan for SequalJS (TypeScript)

## Overview

This document outlines the plan to upgrade the TypeScript ProForma parser from version 2.0 to 2.1, implementing all six new features specified in the ProForma 2.1 specification.

**Reference Implementation**: The Go implementation at `/mnt/d/GoLandProjects/sequal` has already been upgraded to ProForma 2.1 and serves as the reference for this upgrade.

---

## Implementation Phases

### Phase 1: Charged Formulas (Section 11.1)
**Priority**: High
**Files to Modify**: `src/modification.ts`, `src/proforma.ts`

#### Description
Add support for formulas with charge notation, e.g., `[Formula:Zn1:z+2]`, `[Formula:Cu1:z-1]`

#### Implementation Details

**1.1 PipeValue Class Enhancement** (`src/modification.ts`)
```typescript
class PipeValue {
  // Add new fields
  charge?: string;          // e.g., "z+2", "z-1"
  chargeValue?: number;     // e.g., 2, -1

  // Update constructor to parse charge notation
  constructor(value: string, valueType: PipeValueType) {
    // Existing code...

    // ProForma 2.1: Handle charged formulas (Section 11.1)
    if (valueType === PipeValueType.FORMULA) {
      const chargeMatch = value.match(/:z([+-]\d+)$/);
      if (chargeMatch) {
        this.charge = 'z' + chargeMatch[1];
        this.chargeValue = parseInt(chargeMatch[1]);
        this.value = value.replace(/:z[+-]\d+$/, '');
      }
    }
  }

  // Add getters
  getCharge(): string | undefined { return this.charge; }
  getChargeValue(): number | undefined { return this.chargeValue; }
}
```

**1.2 Update toProforma() Method**
```typescript
toProforma(): string {
  let result = this.value;

  // ProForma 2.1: Add charge notation
  if (this.charge) {
    result += ':' + this.charge;
  }

  return result;
}
```

**1.3 Test Cases** (`src/__test__/proforma_2_1_charged_formulas.test.ts`)
- Basic charged formula: `PEPT[Formula:Zn1:z+2]IDE`
- Negative charge: `PEPT[Formula:Cu1:z-1]IDE`
- Multiple charged formulas in sequence
- Round-trip conversion
- Combined with other modifications

**Expected Outcome**: Parser can handle formulas with `:z+N` or `:z-N` charge notation

---

### Phase 2: Named Entity Definitions (Section 11.3)
**Priority**: High
**Files to Modify**: `src/modification.ts`, `src/proforma.ts`

#### Description
Support named entity references like `[#g1:Glycan]`, `[#p1:Peptide]`

#### Implementation Details

**2.1 Modification Class Enhancement** (`src/modification.ts`)
```typescript
class Modification extends BaseBlock {
  // Add new field
  fullName?: string;  // Named entity full name

  constructor(params) {
    // Existing code...
    this.fullName = params.fullName;
  }

  // Add getter
  getFullName(): string | undefined { return this.fullName; }
}
```

**2.2 Parser Enhancement** (`src/proforma.ts`)
```typescript
static _createModification(modStr: string, options: any): Modification {
  // Check for named entity pattern: #id:Name
  const namedEntityMatch = modStr.match(/^#([^:]+):(.+)$/);
  if (namedEntityMatch) {
    const [, entityId, entityName] = namedEntityMatch;

    return new Modification({
      value: modStr,
      fullName: entityName,
      modType: options.modType || 'static',
      // ... other params
    });
  }

  // Existing code...
}
```

**2.3 Update Serialization**
```typescript
toProforma(): string {
  if (this.fullName) {
    // Already in #id:Name format
    return this.value;
  }

  // Existing code...
}
```

**2.4 Test Cases** (`src/__test__/proforma_2_1_named_entities.test.ts`)
- Basic named entity: `[#g1:Glycan]-PEPTIDE`
- Multiple named entities
- Named entities with modifications
- Round-trip conversion

**Expected Outcome**: Parser recognizes and preserves named entity definitions

---

### Phase 3: Custom Monosaccharide Definitions (Section 11.4)
**Priority**: Medium
**Files to Modify**: `src/modification.ts`, `src/proforma.ts`

#### Description
Support custom glycan building blocks: `[#d1:dHex]`, `[#h1:Hex(5)]`

#### Implementation Details

**3.1 Same approach as Named Entities**
- Custom monosaccharides use the same `#id:Name` syntax
- Already handled by Phase 2 implementation
- Just need specific validation for glycan context

**3.2 Additional Validation** (`src/modification.ts`)
```typescript
// In Modification constructor
if (this.fullName && this.modType === 'glycan') {
  // Validate glycan definition format
  this.validateGlycanDefinition();
}

private validateGlycanDefinition(): void {
  // Check for valid glycan composition
  const glycanPattern = /^[A-Za-z]+(\(\d+\))?$/;
  if (!glycanPattern.test(this.fullName!)) {
    throw new Error(`Invalid glycan definition: ${this.fullName}`);
  }
}
```

**3.3 Test Cases** (`src/__test__/proforma_2_1_custom_monosaccharides.test.ts`)
- Custom monosaccharide: `[#d1:dHex]`
- With counts: `[#h1:Hex(5)]`
- Multiple custom definitions
- Round-trip conversion

**Expected Outcome**: Parser handles custom glycan building block definitions

---

### Phase 4: Terminal Global Modifications (Section 11.5)
**Priority**: Medium
**Files to Modify**: `src/proforma.ts`

#### Description
Allow global modifications on termini: `<[Acetyl]@N-term>`, `<[Amidation]@C-term>`

#### Implementation Details

**4.1 Parser Enhancement** (`src/proforma.ts`)
```typescript
// In global modification parsing section
const globalModMatch = cleanedProforma.match(/^<\[([^\]]+)\]@([^>]+)>/);
if (globalModMatch) {
  const [fullMatch, modValue, targets] = globalModMatch;

  // ProForma 2.1: Check for terminal targets
  if (targets === 'N-term' || targets === 'C-term') {
    // Create terminal modification instead of global
    const terminalMod = this._createModification(modValue, {
      modType: 'terminal'
    });

    // Add to appropriate terminal position
    if (targets === 'N-term') {
      modifications.set(-1, [terminalMod]);
    } else {
      modifications.set(-2, [terminalMod]);
    }
  } else {
    // Existing global mod handling
    // ...
  }
}
```

**4.2 Test Cases** (`src/__test__/proforma_2_1_terminal_global.test.ts`)
- N-terminal global: `<[Acetyl]@N-term>PEPTIDE`
- C-terminal global: `<[Amidation]@C-term>PEPTIDE`
- Both terminals
- Round-trip conversion

**Expected Outcome**: Global modification syntax works for terminal positions

---

### Phase 5: Placement Controls (Section 11.2)
**Priority**: High
**Files to Modify**: `src/modification.ts`, `src/proforma.ts`

#### Description
Add placement control tags for global modifications:
- `Position:M` - Position constraint
- `Limit:2` - Max modifications per position
- `CoMKP` - Colocalize with known positions
- `CoMUP` - Colocalize with unknown positions

#### Implementation Details

**5.1 Modification Class Enhancement** (`src/modification.ts`)
```typescript
class Modification extends BaseBlock {
  // ProForma 2.1: Placement controls (Section 11.2)
  positionConstraint?: string[];    // e.g., ["M"], ["S", "T", "Y"]
  limitPerPosition?: number;        // e.g., 2
  colocalizeKnown: boolean = false; // CoMKP
  colocalizeUnknown: boolean = false; // CoMUP

  constructor(params) {
    // Existing code...
    this.positionConstraint = params.positionConstraint;
    this.limitPerPosition = params.limitPerPosition;
    this.colocalizeKnown = params.colocalizeKnown ?? false;
    this.colocalizeUnknown = params.colocalizeUnknown ?? false;
  }

  // Add getters
  getPositionConstraint(): string[] | undefined { return this.positionConstraint; }
  getLimitPerPosition(): number | undefined { return this.limitPerPosition; }
  getColocalizeKnown(): boolean { return this.colocalizeKnown; }
  getColocalizeUnknown(): boolean { return this.colocalizeUnknown; }
}
```

**5.2 GlobalModification Class Update**
```typescript
class GlobalModification extends Modification {
  constructor(params) {
    super({
      ...params,
      positionConstraint: params.positionConstraint,
      limitPerPosition: params.limitPerPosition,
      colocalizeKnown: params.colocalizeKnown,
      colocalizeUnknown: params.colocalizeUnknown,
    });
  }
}
```

**5.3 Parser Enhancement** (`src/proforma.ts`)
```typescript
// In global modification parsing
if (modValue.includes('|')) {
  const modParts = modValue.split('|');
  const modName = modParts[0];

  // ProForma 2.1: Parse placement control tags
  let positionConstraint: string[] | undefined;
  let limitPerPosition: number | undefined;
  let colocalizeKnown = false;
  let colocalizeUnknown = false;

  for (let i = 1; i < modParts.length; i++) {
    const part = modParts[i];

    if (part.startsWith('Position:')) {
      positionConstraint = part.substring(9).split(',');
    } else if (part.startsWith('Limit:')) {
      limitPerPosition = parseInt(part.substring(6));
    } else if (part === 'CoMKP' || part === 'ColocaliseModificationsOfKnownPosition') {
      colocalizeKnown = true;
    } else if (part === 'CoMUP' || part === 'ColocaliseModificationsOfUnknownPosition') {
      colocalizeUnknown = true;
    }
  }

  // Create global modification with placement controls
  globalMods.push(new GlobalModification({
    value: modName,
    targetResidues: targets.split(','),
    globalModType: 'fixed',
    positionConstraint,
    limitPerPosition,
    colocalizeKnown,
    colocalizeUnknown,
  }));
}
```

**5.4 Serialization Update** (`src/modification.ts`)
```typescript
toProforma(): string {
  let result = this.modValue.toProforma();

  // ProForma 2.1: Add placement control tags
  const tags: string[] = [];

  if (this.positionConstraint && this.positionConstraint.length > 0) {
    tags.push(`Position:${this.positionConstraint.join(',')}`);
  }

  if (this.limitPerPosition !== undefined) {
    tags.push(`Limit:${this.limitPerPosition}`);
  }

  if (this.colocalizeKnown) {
    tags.push('CoMKP');
  }

  if (this.colocalizeUnknown) {
    tags.push('CoMUP');
  }

  if (tags.length > 0) {
    result += '|' + tags.join('|');
  }

  return result;
}
```

**5.5 Test Cases** (`src/__test__/proforma_2_1_placement_controls.test.ts`)
- Position constraint: `<[Oxidation|Position:M]@M>PEPTIDE`
- Multiple positions: `<[Phospho|Position:S,T,Y]@S,T,Y>PEPTIDES`
- Limit: `<[Phospho|Limit:2]@S,T,Y>STYSTY`
- CoMKP: `<[Oxidation|CoMKP]@M>PEPTIDE`
- CoMUP: `<[Oxidation|CoMUP]@M>PEPTIDE`
- Combined: `<[Phospho|Position:S,T,Y|Limit:2|CoMKP]@S,T,Y>PEPTIDES`
- Serialization tests
- Round-trip tests

**Expected Outcome**: Full placement control support for global modifications

---

### Phase 6: Ion Notation (Section 11.6)
**Priority**: High
**Files to Modify**: `src/modification.ts`, `src/proforma.ts`

#### Description
Add semantic flag for ion type modifications: `[a-type-ion]`, `[b-type-ion]`, etc.

#### Implementation Details

**6.1 Modification Class Enhancement** (`src/modification.ts`)
```typescript
class Modification extends BaseBlock {
  // ProForma 2.1: Ion notation (Section 11.6)
  isIonType: boolean = false;

  constructor(params) {
    // Existing code...
    this.isIonType = params.isIonType ?? false;
  }

  // Add getter
  getIsIonType(): boolean { return this.isIonType; }
}
```

**6.2 Ion Type Detection** (`src/proforma.ts`)
```typescript
static isIonTypeModification(modStr: string): boolean {
  const modStrLower = modStr.toLowerCase();

  // Check for -type-ion suffix
  if (modStrLower.endsWith('-type-ion')) {
    return true;
  }

  // Known Unimod ion type IDs
  const ionTypeUnimodIds = new Set([
    '140',   // a-type-ion
    '2132',  // b-type-ion
    '4',     // c-type-ion
    '24',    // x-type-ion
    '2133',  // y-type-ion
    '23',    // z-type-ion
  ]);

  // Check for Unimod references
  if (modStr.startsWith('UNIMOD:') || modStr.startsWith('U:')) {
    const unimodId = modStr.split(':')[1];
    return ionTypeUnimodIds.has(unimodId);
  }

  return false;
}
```

**6.3 Parser Integration**
```typescript
static _createModification(modStr: string, options: any): Modification {
  const isIonType = this.isIonTypeModification(modStr);

  return new Modification({
    value: modStr,
    isIonType,
    // ... other params
  });
}
```

**6.4 Test Cases** (`src/__test__/proforma_2_1_ion_notation.test.ts`)
- All ion types: `[a-type-ion]`, `[b-type-ion]`, `[c-type-ion]`, `[x-type-ion]`, `[y-type-ion]`, `[z-type-ion]`
- Unimod references: `[UNIMOD:140]`, `[UNIMOD:2132]`
- Short form: `[U:140]`, `[U:2132]`
- Case insensitive: `[A-TYPE-ION]`, `[B-Type-Ion]`
- Non-ion mods return false
- Round-trip preservation

**Expected Outcome**: Ion type modifications are flagged and preserved

---

## Integration Testing

### Create Comprehensive Integration Test Suite
**File**: `src/__test__/proforma_2_1_integration.test.ts`

#### Test Coverage
1. **Placement Controls with Ion Notation**
   - `<[Phospho|Position:S,T,Y|Limit:2]@S,T,Y>PEPT[a-type-ion]IDE`

2. **Charged Formulas with Ion Types**
   - `PE[a-type-ion]PT[Formula:Zn1:z+2]IDE`

3. **Named Entities with Charged Formulas**
   - `[#g1:Glycan]-PEPT[Formula:Zn1:z+2]IDE`

4. **Complex Global Modifications**
   - `<[Phospho|Position:S,T,Y|CoMKP]@S,T,Y><[Oxidation|Position:M|CoMUP]@M>STMYST`

5. **All Features Combined**
   - `<[Phospho|Position:S,T,Y|Limit:2|CoMKP]@S,T,Y>[#g1:Glycan]-PE[a-type-ion]PT[Formula:Zn1:z+2]IDE`

6. **Serialization Consistency**
   - Multiple round-trip tests ensuring stability

7. **Edge Cases**
   - Empty position constraints
   - Multiple ion types in sequence
   - Terminal global with placement controls

---

## Testing Strategy

### 1. Unit Tests
- Each phase gets its own test file
- Minimum 10-15 test cases per feature
- Cover parsing, serialization, and round-trip

### 2. Integration Tests
- Test feature combinations
- Complex real-world scenarios
- Round-trip stability

### 3. Regression Tests
- **CRITICAL**: Run all existing ProForma 2.0 tests after each phase
- Ensure zero breaking changes
- Fix any regressions immediately before proceeding

### 4. Test Naming Convention
```
src/__test__/
  proforma_2_1_charged_formulas.test.ts
  proforma_2_1_named_entities.test.ts
  proforma_2_1_custom_monosaccharides.test.ts
  proforma_2_1_terminal_global.test.ts
  proforma_2_1_placement_controls.test.ts
  proforma_2_1_ion_notation.test.ts
  proforma_2_1_integration.test.ts
```

---

## Documentation Updates

### 1. README.md Updates
- Change version from "ProForma 2.0" to "ProForma 2.1"
- Add ProForma 2.1 Features section with examples
- Update feature list
- Add 2.1 code examples
- Update compliance section

### 2. Code Documentation
- Add JSDoc comments for new fields
- Document ProForma 2.1 sections in comments
- Add examples in class documentation

### 3. CHANGELOG.md
Create new file documenting:
- Version bump to 2.1.0
- All new features
- Breaking changes (if any)
- Migration guide

---

## Implementation Order

### Recommended Sequence
1. **Phase 1: Charged Formulas** - Foundation for formula enhancements
2. **Phase 6: Ion Notation** - Simple boolean flag, minimal risk
3. **Phase 5: Placement Controls** - Core feature, test thoroughly
4. **Phase 2: Named Entities** - Moderate complexity
5. **Phase 3: Custom Monosaccharides** - Builds on named entities
6. **Phase 4: Terminal Global** - Lower priority, niche use case
7. **Integration Tests** - Comprehensive feature combinations

---

## Success Criteria

### Phase Completion Checklist
For each phase:
- [ ] Feature implementation complete
- [ ] Unit tests written (10+ test cases)
- [ ] All unit tests passing
- [ ] All existing ProForma 2.0 tests still passing (zero regressions)
- [ ] Serialization/round-trip tests passing
- [ ] Code documentation updated
- [ ] Examples added to README (if applicable)

### Final Completion
- [ ] All 6 phases implemented
- [ ] Integration test suite complete (20+ test cases)
- [ ] All tests passing (100% pass rate)
- [ ] Zero regressions in ProForma 2.0 features
- [ ] README.md updated
- [ ] CHANGELOG.md created
- [ ] Version bumped to 2.1.0 in package.json

---

## Risk Mitigation

### Critical Considerations
1. **Backward Compatibility**: Must maintain 100% ProForma 2.0 compatibility
2. **Pipe Value Handling**: Don't break existing pipe value parsing for placement controls
3. **Terminal Modifications**: Ensure terminal global mods don't conflict with existing terminal mod handling
4. **TypeScript Types**: Add proper type definitions for all new fields
5. **Jest Configuration**: Ensure test coverage includes new files

### Testing Checkpoints
- After each phase: Run full test suite
- Before integration tests: Verify zero regressions
- Before documentation: Verify all features work in combination
- Before release: Full manual testing with complex examples

---

## Reference Implementation

**Go Implementation**: `/mnt/d/GoLandProjects/sequal`
- All 6 phases completed
- 37+ new tests
- Zero regressions
- Can be used as reference for TypeScript port

**Key Files to Reference**:
- `sequal/proforma.go` - Parser implementation
- `sequal/modification.go` - Modification data structures
- `sequal/global_modification.go` - Global modification handling
- `sequal/proforma_2_1_*_test.go` - Test patterns and cases

---

## Timeline Estimate

**Per Phase**: 2-4 hours (implementation + testing)
**Total Estimate**: 12-24 hours for complete upgrade

**Breakdown**:
- Phase 1 (Charged Formulas): 2 hours
- Phase 2 (Named Entities): 3 hours
- Phase 3 (Custom Monosaccharides): 2 hours
- Phase 4 (Terminal Global): 2 hours
- Phase 5 (Placement Controls): 4 hours
- Phase 6 (Ion Notation): 2 hours
- Integration Tests: 3 hours
- Documentation: 2 hours
- Final QA: 2 hours

---

## Notes

- Use strict TypeScript typing for all new fields
- Follow existing code style and patterns
- Maintain JSDoc documentation standards
- Use Jest for all testing
- Keep test files focused and organized
- Commit after each phase completion
- Tag release as v2.1.0 when complete
