vodia-pharmacy-ai 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const sqlite3 = require('sqlite3').verbose();
4
+
5
+ const db = new sqlite3.Database('./pharmacy.db');
6
+
7
+ function columnExists(tableName, columnName) {
8
+ return new Promise(function (resolve, reject) {
9
+ db.all(`PRAGMA table_info(${tableName})`, [], function (err, rows) {
10
+ if (err) return reject(err);
11
+
12
+ const exists = rows.some(function (row) {
13
+ return row.name === columnName;
14
+ });
15
+
16
+ resolve(exists);
17
+ });
18
+ });
19
+ }
20
+
21
+ function runSql(sql) {
22
+ return new Promise(function (resolve, reject) {
23
+ db.run(sql, function (err) {
24
+ if (err) return reject(err);
25
+ resolve();
26
+ });
27
+ });
28
+ }
29
+
30
+ async function main() {
31
+ console.log('Adding portal fields if missing...');
32
+
33
+ if (!(await columnExists('refill_requests', 'agent_notes'))) {
34
+ await runSql(`ALTER TABLE refill_requests ADD COLUMN agent_notes TEXT`);
35
+ console.log('Added agent_notes');
36
+ }
37
+
38
+ if (!(await columnExists('refill_requests', 'fulfilled_at'))) {
39
+ await runSql(`ALTER TABLE refill_requests ADD COLUMN fulfilled_at TEXT`);
40
+ console.log('Added fulfilled_at');
41
+ }
42
+
43
+ if (!(await columnExists('refill_requests', 'updated_at'))) {
44
+ await runSql(`ALTER TABLE refill_requests ADD COLUMN updated_at TEXT`);
45
+ console.log('Added updated_at');
46
+ }
47
+
48
+ console.log('Portal fields are ready.');
49
+ }
50
+
51
+ main()
52
+ .catch(function (err) {
53
+ console.error('Migration failed:', err.message);
54
+ process.exit(1);
55
+ })
56
+ .finally(function () {
57
+ db.close();
58
+ });
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ const axios = require('axios');
4
+
5
+ function safeString(value) {
6
+ return String(value || '').trim();
7
+ }
8
+
9
+ function truncateJson(value) {
10
+ try {
11
+ const raw = JSON.stringify(value || {});
12
+ return raw.length > 20000 ? raw.slice(0, 20000) : raw;
13
+ } catch (err) {
14
+ return '{}';
15
+ }
16
+ }
17
+
18
+ function parseUsAddress(address) {
19
+ const raw = safeString(address);
20
+ const parts = raw.split(',').map(function (part) {
21
+ return part.trim();
22
+ }).filter(Boolean);
23
+
24
+ const parsed = {
25
+ street: raw,
26
+ city: '',
27
+ state: '',
28
+ zipcode: ''
29
+ };
30
+
31
+ if (parts.length >= 3) {
32
+ parsed.street = parts[0];
33
+ parsed.city = parts[1];
34
+
35
+ const last = parts.slice(2).join(' ');
36
+ const match = last.match(/\b([A-Z]{2})\b\s*(\d{5}(?:-\d{4})?)?/i);
37
+
38
+ if (match) {
39
+ parsed.state = match[1].toUpperCase();
40
+ parsed.zipcode = match[2] || '';
41
+ }
42
+ }
43
+
44
+ return parsed;
45
+ }
46
+
47
+ function skipped(reason, address) {
48
+ return {
49
+ attempted: false,
50
+ valid: false,
51
+ provider: process.env.ADDRESS_VALIDATION_PROVIDER || 'disabled',
52
+ status: reason,
53
+ original_address: safeString(address),
54
+ standardized_address: '',
55
+ raw_json: {}
56
+ };
57
+ }
58
+
59
+ async function validateWithSmarty(address) {
60
+ const authId = safeString(process.env.SMARTY_AUTH_ID);
61
+ const authToken = safeString(process.env.SMARTY_AUTH_TOKEN);
62
+
63
+ if (!authId || !authToken) {
64
+ return skipped('smarty_missing_credentials', address);
65
+ }
66
+
67
+ const parsed = parseUsAddress(address);
68
+
69
+ const response = await axios.get('https://us-street.api.smarty.com/street-address', {
70
+ timeout: 7000,
71
+ params: {
72
+ 'auth-id': authId,
73
+ 'auth-token': authToken,
74
+ street: parsed.street,
75
+ city: parsed.city,
76
+ state: parsed.state,
77
+ zipcode: parsed.zipcode,
78
+ candidates: 1,
79
+ match: 'enhanced'
80
+ }
81
+ });
82
+
83
+ const candidates = Array.isArray(response.data) ? response.data : [];
84
+ const first = candidates[0];
85
+
86
+ if (!first) {
87
+ return {
88
+ attempted: true,
89
+ valid: false,
90
+ provider: 'smarty',
91
+ status: 'not_found',
92
+ original_address: safeString(address),
93
+ standardized_address: '',
94
+ raw_json: response.data
95
+ };
96
+ }
97
+
98
+ const analysis = first.analysis || {};
99
+ const dpv = safeString(analysis.dpv_match_code).toUpperCase();
100
+
101
+ const valid = ['Y', 'S', 'D'].includes(dpv);
102
+ const standardizedAddress = [
103
+ first.delivery_line_1,
104
+ first.last_line
105
+ ].filter(Boolean).join(', ');
106
+
107
+ return {
108
+ attempted: true,
109
+ valid: valid,
110
+ provider: 'smarty',
111
+ status: valid ? 'validated' : 'needs_review',
112
+ original_address: safeString(address),
113
+ standardized_address: standardizedAddress,
114
+ raw_json: response.data
115
+ };
116
+ }
117
+
118
+ async function validateAddress(address) {
119
+ const raw = safeString(address);
120
+
121
+ if (!raw || raw.toUpperCase() === 'UNKNOWN') {
122
+ return skipped('address_missing', address);
123
+ }
124
+
125
+ const provider = safeString(process.env.ADDRESS_VALIDATION_PROVIDER || 'disabled').toLowerCase();
126
+
127
+ if (provider === 'disabled' || provider === 'none') {
128
+ return skipped('disabled', address);
129
+ }
130
+
131
+ try {
132
+ if (provider === 'smarty') {
133
+ return await validateWithSmarty(raw);
134
+ }
135
+
136
+ return skipped('unknown_provider_' + provider, address);
137
+ } catch (err) {
138
+ return {
139
+ attempted: true,
140
+ valid: false,
141
+ provider: provider,
142
+ status: 'provider_error',
143
+ original_address: raw,
144
+ standardized_address: '',
145
+ error: err.message,
146
+ raw_json: err.response && err.response.data ? err.response.data : {}
147
+ };
148
+ }
149
+ }
150
+
151
+ module.exports = {
152
+ validateAddress,
153
+ truncateJson
154
+ };