Skip to content
Open
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
183 changes: 183 additions & 0 deletions scripts/parse-sensors.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env node
/**
* Parse sensors.ts and convert to JSON using regex
* This doesn't require importing the TypeScript modules
*/

import { readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const sensorsPath = join(__dirname, '../src/app/data/catalogs/sensors.ts');
const outputPath = join(__dirname, '../src/app/data/catalogs/sensors.json');

// Read the file
const content = readFileSync(sensorsPath, 'utf-8');

// Extract the sensors object
const match = content.match(/export const sensors = <SensorList>\{([\s\S]*?)\n\};/);

if (!match) {
console.error('Could not find sensors object');
process.exit(1);
}

const sensorsContent = match[1];

// Split into individual sensor blocks
const sensorBlocks = [];
let currentBlock = '';
let braceCount = 0;
let inSensor = false;
let currentKey = '';

const lines = sensorsContent.split('\n');

for (const line of lines) {
const trimmed = line.trim();

// Check if this is the start of a sensor definition
const sensorMatch = trimmed.match(/^(\w+):\s*new\s+(RfSensor|DetailedSensor)\(\{/);

if (sensorMatch) {
if (currentBlock) {
sensorBlocks.push({ key: currentKey, type: currentBlock.type, content: currentBlock.content });
}
currentKey = sensorMatch[1];
currentBlock = { type: sensorMatch[2], content: '' };
inSensor = true;
braceCount = 1;
continue;
}

if (inSensor) {
// Count braces
for (const char of line) {
if (char === '{') {
braceCount++;
}
if (char === '}') {
braceCount--;
}
}

if (braceCount === 0) {
// End of sensor definition
inSensor = false;
} else {
currentBlock.content += line + '\n';
}
}
}

// Don't forget the last block
if (currentBlock && currentKey) {
sensorBlocks.push({ key: currentKey, type: currentBlock.type, content: currentBlock.content });
}

// Parse each sensor block
const sensors = {};

for (const block of sensorBlocks) {
const sensor = {
_sensorType: block.type,
};

const lines = block.content.split('\n');

for (const line of lines) {
const trimmed = line.trim();

if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
continue;
}

// Match property: value pattern - more flexible to capture full values
const propMatch = trimmed.match(/^(\w+):\s*(.+)$/);

if (propMatch) {
let [, key, value] = propMatch;

// Remove comments at the end (but only after quoted strings or other values)
// This regex looks for comments that start after a quote, comma, or closing bracket
value = value.replace(/(['"\],}])\s*\/\/.*$/, '$1');
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern for removing comments (value.replace(/(['"\],}])\s*\/\/.*$/, '$1')) at line 106 is too restrictive and may not catch all inline comments. For example, it won't remove comments that appear after numeric values or unquoted identifiers (e.g., maxEl: 90 // comment). Consider using a more comprehensive pattern like /\s*\/\/.*$/ after proper string/array parsing.

Suggested change
value = value.replace(/(['"\],}])\s*\/\/.*$/, '$1');
value = value.replace(/\s*\/\/.*$/, '');

Copilot uses AI. Check for mistakes.

// Remove trailing comma
value = value.replace(/,\s*$/, '');

// Remove type assertions like <Degrees>
value = value.replace(/<\w+>/g, '');

// Remove 'as Type' assertions but preserve content
value = value.replace(/\s+as\s+\w+/g, '');

value = value.trim();

// Parse the value
try {
// Handle arrays
if (value.startsWith('[')) {
// Clean array content
const cleaned = value.replace(/\s+as\s+\w+/g, '');

// Try to parse array content
// Handle enum arrays like [CommLink.AEHF, CommLink.WGS]
const arrayMatch = cleaned.match(/\[(.*)\]/);

if (arrayMatch) {
const content = arrayMatch[1];

// Check if it contains enum values
if (content.includes('.')) {
// It's an enum array, split and keep as strings
sensor[key] = content.split(',').map(item => item.trim());
} else {
// Try to parse as JSON
try {
sensor[key] = JSON.parse(cleaned);
} catch {
sensor[key] = content.split(',').map(item => {
const trimmed = item.trim();

return isNaN(Number(trimmed)) ? trimmed : parseFloat(trimmed);
});
Comment on lines +142 to +146
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parsing logic at line 142-146 has inconsistent handling of array elements. When JSON.parse fails on a cleaned array, the code tries to split by comma and parse individual items, but it doesn't handle quoted strings within the array properly. This could lead to incorrect parsing if array elements contain commas within quoted strings.

Copilot uses AI. Check for mistakes.
}
}
}
}
// Handle booleans
else if (value === 'true') {
sensor[key] = true;
} else if (value === 'false') {
sensor[key] = false;
}
// Handle numbers
else if (/^-?\d+\.?\d*$/.test(value)) {
sensor[key] = parseFloat(value);
}
// Handle strings
else if (value.startsWith("'") || value.startsWith('"')) {
sensor[key] = value.replace(/^['"]|['"]$/g, '');
}
// Handle enum values (keep as string)
else {
sensor[key] = value;
}
} catch (e) {
// If parsing fails, store as string
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The fallback logic for failed parsing wraps values in a try-catch (line 120) but doesn't log or handle the error. When JSON parsing or other conversions fail, the code silently stores the value as a string, which could hide data format issues. Consider logging warnings when parsing fails to help debug potential issues during sensor data conversion.

Suggested change
// If parsing fails, store as string
// If parsing fails, store as string and log a warning
console.warn(`Warning: Failed to parse value for key "${key}". Storing as string. Value: ${JSON.stringify(value)}. Error: ${e.message}`);

Copilot uses AI. Check for mistakes.
sensor[key] = value;
}
}
}

sensors[block.key] = sensor;
}

// Write to JSON
writeFileSync(outputPath, JSON.stringify(sensors, null, 2));

console.log(`✓ Successfully converted ${Object.keys(sensors).length} sensors`);
console.log(`✓ Output: ${outputPath}`);
2 changes: 1 addition & 1 deletion src/app/data/catalog-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

import { controlSites } from '@app/app/data/catalogs/control-sites';
import { launchSiteObjects, launchSites } from '@app/app/data/catalogs/launch-sites';
import { sensors } from '@app/app/data/catalogs/sensors';
import { sensors } from '@app/app/data/catalogs/sensorLoader';
import { stars } from '@app/app/data/catalogs/stars';
import { GetSatType, MissileParams } from '@app/engine/core/interfaces';
import { ServiceLocator } from '@app/engine/core/service-locator';
Expand Down
74 changes: 74 additions & 0 deletions src/app/data/catalogs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Sensor Data Management

## Overview

Sensors are now loaded from a JSON file (`sensors.json`) instead of being hardcoded in TypeScript. This allows for easier migration to remote data sources in the future without requiring code changes.

## Files

- **sensors.json**: Contains all sensor data in JSON format
- **sensorLoader.ts**: Loads sensors from JSON and creates sensor objects
- **sensors.ts**: Legacy TypeScript file (kept for type definitions and enums)
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation states that sensors.ts is a "Legacy TypeScript file (kept for type definitions and enums)" at line 11, but the PR description indicates this is a migration away from TypeScript. It's unclear whether sensors.ts is being kept in the repository or removed. If it's being kept, this should be clarified. If it's being removed, the documentation should be updated to reflect that type definitions are now in sensorLoader.ts.

Copilot uses AI. Check for mistakes.

## Architecture

1. `sensors.json` contains all sensor configurations as plain JSON objects
2. `sensorLoader.ts` reads the JSON file and instantiates `DetailedSensor` or `RfSensor` objects
3. All application code imports sensors from `sensorLoader.ts` instead of `sensors.ts`

## Adding or Modifying Sensors

### Option 1: Edit JSON Directly

Edit `sensors.json` and add/modify sensor entries:

```json
{
"NEWSENSOR": {
"_sensorType": "RfSensor",
"objName": "NEWSENSOR",
"name": "New Sensor Name",
"lat": 40.0,
"lon": -75.0,
"alt": 0.1,
...
}
}
```

### Option 2: Edit TypeScript and Regenerate

1. Edit `sensors.ts` to add/modify sensor definitions
2. Run the conversion script:
```bash
node scripts/parse-sensors.mjs
```
3. This will regenerate `sensors.json` from `sensors.ts`

## Migrating to Remote Sources

To load sensors from a remote URL:

1. Modify `sensorLoader.ts` to fetch JSON from a remote endpoint
2. Keep the same JSON schema
3. No changes needed in application code

Example:
```typescript
// In sensorLoader.ts
const response = await fetch('https://api.example.com/sensors.json');
const sensorsData = await response.json();
```

Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async/await example code for remote loading (lines 58-60) is misleading because the current implementation in sensorLoader.ts uses a synchronous import, not an async fetch. If the intention is to support remote loading, the loadSensorsFromJson() function would need to be made async and all calling code would need to be updated. Consider clarifying that this is a future enhancement or providing a more complete example of the necessary changes.

Suggested change
**Note:** The current implementation in `sensorLoader.ts` loads sensors synchronously from a local JSON file. To support remote loading as shown above, you would need to make the loader function (`loadSensorsFromJson()`) asynchronous and update all calling code to use `await` or handle Promises. This is a future enhancement and not currently supported.

Copilot uses AI. Check for mistakes.
## Type Safety

The `sensorLoader.ts` module:
- Converts string enum values (e.g., `"SpaceObjectType.OPTICAL"`) to actual enum values
- Ensures proper type casting for `Degrees`, `Kilometers`, etc.
- Creates instances of `DetailedSensor` or `RfSensor` based on `_sensorType` field

## Notes

- The `_sensorType` field in JSON determines whether to create a `DetailedSensor` or `RfSensor`
- Enum values in JSON are stored as strings (e.g., `"CommLink.AEHF"`) and converted at load time
- All application code should import from `sensorLoader` not `sensors`
Loading
Loading