```ts
export function normalizePartCode(code: string): string {
  if (typeof code !== 'string') {
    throw new Error('Input must be a string');
  }

  // Remove spaces and hyphens, convert to uppercase
  const cleaned = code.replace(/[-\s]/g, '').toUpperCase();

  // Validate characters
  if (!/^[0-9A-Z]+$/.test(cleaned)) {
    throw new Error('Invalid characters in input');
  }

  // Check length
  if (cleaned.length !== 9) {
    throw new Error('Input must be 9 characters long after cleaning');
  }

  // Extract payload and check character
  const payload = cleaned.slice(0, 8);
  const checkChar = cleaned[8];

  // Calculate the expected check character
  const weights = [3, 5, 7];
  let sum = 0;
  for (let i = 0; i < payload.length; i++) {
    const value = /[0-9]/.test(payload[i]) ? parseInt(payload[i], 10) : payload.charCodeAt(i) - 'A'.charCodeAt(0) + 10;
    sum += value * weights[i % weights.length];
  }
  const expectedCheckValue = sum % 36;
  const expectedCheckChar = expectedCheckValue < 10 ? expectedCheckValue.toString() : String.fromCharCode('A'.charCodeAt(0) + expectedCheckValue - 10);

  // Verify check character
  if (checkChar !== expectedCheckChar) {
    throw new Error('Invalid check character');
  }

  // Return canonical rendering
  return `${payload.slice(0, 4)}-${payload.slice(4, 8)}-${checkChar}`;
}
```