File size: 4,747 Bytes
cb558b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
'use strict';

const fs = require('fs');
const path = require('path');
const { parse } = require('csv-parse/sync');
const { stringify } = require('csv-stringify/sync');

const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '../../data');
const CACHE_FILE = path.join(DATA_DIR, 'gap1_cache.csv');
const HEADERS = ['brand_name_lc','generic_name','active_ingredients','drug_a','drug_b','severity','side_effects','mechanism','clinical_action','confidence','created_at'];

class Gap1Cache {
  constructor() {
    // brand_lc → { generic_name, active_ingredients, interactions: Map(drug_b → {severity, ...}) }
    this.medicines = new Map();
    // 'drug_a|drug_b' → { severity, side_effects, mechanism, clinical_action }
    this.pairIndex = new Map();
    this._load();
  }

  _load() {
    if (!fs.existsSync(CACHE_FILE)) {
      fs.writeFileSync(CACHE_FILE, HEADERS.join(',') + '\n');
      return;
    }
    try {
      const raw = fs.readFileSync(CACHE_FILE);
      const rows = parse(raw, { columns: true, skip_empty_lines: true });
      for (const r of rows) {
        this._indexRow(r);
      }
      console.log(`[gap1Cache] loaded ${rows.length} cached rows`);
    } catch (e) {
      console.error('[gap1Cache] load error:', e.message);
    }
  }

  _indexRow(r) {
    const brandLc = r.brand_name_lc;
    if (!this.medicines.has(brandLc)) {
      this.medicines.set(brandLc, {
        generic_name: r.generic_name,
        active_ingredients: r.active_ingredients ? r.active_ingredients.split('|') : [],
        interactions: new Map(),
      });
    }
    if (r.drug_b) {
      this.medicines.get(brandLc).interactions.set(r.drug_b, {
        severity: r.severity,
        side_effects: r.side_effects,
        mechanism: r.mechanism,
        clinical_action: r.clinical_action,
      });
      const [da, db] = r.drug_a < r.drug_b ? [r.drug_a, r.drug_b] : [r.drug_b, r.drug_a];
      this.pairIndex.set(`${da}|${db}`, {
        severity: r.severity,
        side_effects: r.side_effects,
        mechanism: r.mechanism,
        clinical_action: r.clinical_action,
      });
    }
  }

  getMedicine(brandName) {
    return this.medicines.get(brandName.toLowerCase()) || null;
  }

  getPair(drugA, drugB) {
    const [da, db] = drugA < drugB ? [drugA, drugB] : [drugB, drugA];
    return this.pairIndex.get(`${da}|${db}`) || null;
  }

  save(brandName, geminiData) {
    const brandLc = brandName.toLowerCase();
    const activeIngs = geminiData.active_ingredients || [];
    const allInteractions = [
      ...(geminiData.prescription_interactions || []),
      ...(geminiData.common_interactions || []),
    ];

    const rows = [];
    const now = new Date().toISOString().split('T')[0];

    if (allInteractions.length === 0) {
      // Save medicine with no interactions found
      rows.push({
        brand_name_lc: brandLc,
        generic_name: geminiData.generic_name || '',
        active_ingredients: activeIngs.join('|'),
        drug_a: brandLc,
        drug_b: '',
        severity: '',
        side_effects: '',
        mechanism: '',
        clinical_action: '',
        confidence: geminiData.confidence || 0,
        created_at: now,
      });
    } else {
      for (const it of allInteractions) {
        if (!it.drug || it.severity === 'Safe') continue;
        rows.push({
          brand_name_lc: brandLc,
          generic_name: geminiData.generic_name || '',
          active_ingredients: activeIngs.join('|'),
          drug_a: brandLc,
          drug_b: it.drug.toLowerCase(),
          severity: it.severity,
          side_effects: it.side_effects || '',
          mechanism: it.mechanism || '',
          clinical_action: it.clinical_action || '',
          confidence: geminiData.confidence || 0,
          created_at: now,
        });
      }
      // If every interaction was filtered out, still register the medicine
      // so future queries hit the cache and the brand resolves correctly.
      if (rows.length === 0) {
        rows.push({
          brand_name_lc: brandLc,
          generic_name: geminiData.generic_name || '',
          active_ingredients: activeIngs.join('|'),
          drug_a: brandLc,
          drug_b: '',
          severity: '',
          side_effects: '',
          mechanism: '',
          clinical_action: '',
          confidence: geminiData.confidence || 0,
          created_at: now,
        });
      }
    }

    // Append to CSV
    const csv = stringify(rows, { header: false, columns: HEADERS });
    fs.appendFileSync(CACHE_FILE, csv);

    // Index in memory
    for (const r of rows) this._indexRow(r);

    console.log(`[gap1Cache] saved ${rows.length} rows for "${brandName}"`);
    return rows.length;
  }
}

module.exports = { Gap1Cache };