feat(api): add genetics.js — DNA panel CRUD + pairing-risk endpoint
This commit is contained in:
158
server/routes/genetics.js
Normal file
158
server/routes/genetics.js
Normal file
@@ -0,0 +1,158 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { getDatabase } = require('../db/init');
|
||||
|
||||
// Golden Retriever panel markers tracked by Breedr
|
||||
const GR_MARKERS = [
|
||||
'PRA1', 'PRA2', 'prcd-PRA', 'GR-PRA1', 'GR-PRA2',
|
||||
'ICH1', 'ICH2', 'NCL', 'DM', 'MD'
|
||||
];
|
||||
|
||||
// GET all genetic tests for a dog
|
||||
router.get('/dog/:dogId', (req, res) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const tests = db.prepare(`
|
||||
SELECT * FROM genetic_tests
|
||||
WHERE dog_id = ?
|
||||
ORDER BY marker ASC
|
||||
`).all(req.params.dogId);
|
||||
|
||||
// Return a full panel including not_tested placeholders
|
||||
const byMarker = {};
|
||||
for (const t of tests) byMarker[t.marker] = t;
|
||||
|
||||
const panel = GR_MARKERS.map(marker => ({
|
||||
marker,
|
||||
...(byMarker[marker] || { result: 'not_tested', dog_id: Number(req.params.dogId) })
|
||||
}));
|
||||
|
||||
res.json({ tests, panel });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET pairing risk — compare sire + dam carrier status
|
||||
// Usage: GET /api/genetics/pairing-risk?sireId=1&damId=2
|
||||
router.get('/pairing-risk', (req, res) => {
|
||||
try {
|
||||
const { sireId, damId } = req.query;
|
||||
if (!sireId || !damId) {
|
||||
return res.status(400).json({ error: 'sireId and damId are required' });
|
||||
}
|
||||
|
||||
const db = getDatabase();
|
||||
|
||||
const getResults = (dogId) => {
|
||||
const rows = db.prepare('SELECT marker, result FROM genetic_tests WHERE dog_id = ?').all(dogId);
|
||||
const map = {};
|
||||
for (const r of rows) map[r.marker] = r.result;
|
||||
return map;
|
||||
};
|
||||
|
||||
const sireResults = getResults(sireId);
|
||||
const damResults = getResults(damId);
|
||||
|
||||
const risks = [];
|
||||
for (const marker of GR_MARKERS) {
|
||||
const s = sireResults[marker] || 'not_tested';
|
||||
const d = damResults[marker] || 'not_tested';
|
||||
|
||||
// Both affected or carrier x carrier = risk
|
||||
if (
|
||||
(s === 'affected' || d === 'affected') ||
|
||||
(s === 'carrier' && d === 'carrier')
|
||||
) {
|
||||
risks.push({
|
||||
marker,
|
||||
sire_result: s,
|
||||
dam_result: d,
|
||||
risk_level: (s === 'affected' || d === 'affected') ? 'high' : 'moderate',
|
||||
note: s === 'affected' || d === 'affected'
|
||||
? 'One or both parents are affected — do not breed'
|
||||
: 'Both parents are carriers — 25% chance of affected offspring',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
sire_id: Number(sireId),
|
||||
dam_id: Number(damId),
|
||||
risks,
|
||||
safe_to_pair: risks.length === 0,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET single genetic test
|
||||
router.get('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const test = db.prepare('SELECT * FROM genetic_tests WHERE id = ?').get(req.params.id);
|
||||
if (!test) return res.status(404).json({ error: 'Genetic test not found' });
|
||||
res.json(test);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST create genetic test
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const { dog_id, test_provider, marker, result, test_date, document_url, notes } = req.body;
|
||||
|
||||
if (!dog_id || !marker || !result) {
|
||||
return res.status(400).json({ error: 'dog_id, marker, and result are required' });
|
||||
}
|
||||
if (!['clear', 'carrier', 'affected', 'not_tested'].includes(result)) {
|
||||
return res.status(400).json({ error: 'result must be: clear | carrier | affected | not_tested' });
|
||||
}
|
||||
|
||||
const db = getDatabase();
|
||||
const dbResult = db.prepare(`
|
||||
INSERT INTO genetic_tests (dog_id, test_provider, marker, result, test_date, document_url, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(dog_id, test_provider || null, marker, result, test_date || null, document_url || null, notes || null);
|
||||
|
||||
const test = db.prepare('SELECT * FROM genetic_tests WHERE id = ?').get(dbResult.lastInsertRowid);
|
||||
res.status(201).json(test);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT update genetic test
|
||||
router.put('/:id', (req, res) => {
|
||||
try {
|
||||
const { test_provider, marker, result, test_date, document_url, notes } = req.body;
|
||||
|
||||
const db = getDatabase();
|
||||
db.prepare(`
|
||||
UPDATE genetic_tests
|
||||
SET test_provider = ?, marker = ?, result = ?, test_date = ?,
|
||||
document_url = ?, notes = ?, updated_at = datetime('now')
|
||||
WHERE id = ?
|
||||
`).run(test_provider || null, marker, result, test_date || null, document_url || null, notes || null, req.params.id);
|
||||
|
||||
const test = db.prepare('SELECT * FROM genetic_tests WHERE id = ?').get(req.params.id);
|
||||
res.json(test);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE genetic test
|
||||
router.delete('/:id', (req, res) => {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
db.prepare('DELETE FROM genetic_tests WHERE id = ?').run(req.params.id);
|
||||
res.json({ message: 'Genetic test deleted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user