feffdb 0.1__py3-none-any.whl

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.
feffdb/feffpath.py ADDED
@@ -0,0 +1,559 @@
1
+ """
2
+ feffpath provides a simple function to read and use the Feff data
3
+ results in FeffDB. This is a slightly simplified version of the code
4
+ to work with Feff data files in X-ray Larch, and is included here to
5
+ give a standalone for to use the data in FeffDB.
6
+
7
+
8
+ path1 = read_feffdat('feffNNNN.dat')
9
+
10
+ """
11
+ import re
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ import numpy as np
15
+
16
+
17
+ import hashlib
18
+ from base64 import b64encode
19
+ from scipy import constants as consts
20
+ from scipy.interpolate import UnivariateSpline
21
+
22
+ from xraydb import atomic_mass, atomic_symbol
23
+ from pyshortcuts import read_textfile, gformat
24
+
25
+ from .feffdb import FeffDatabase
26
+
27
+ KTOE = 1.e20*consts.hbar**2 / (2*consts.m_e * consts.e)
28
+ ETOK = 1.0/KTOE
29
+
30
+ SMALL_ENERGY = 1.e-6
31
+
32
+
33
+ FDAT_ARRS = ('real_phc', 'mag_feff', 'pha_feff', 'red_fact',
34
+ 'lam', 'rep', 'pha', 'amp', 'k')
35
+
36
+ # values that will be available in calculations of Path Parameter values
37
+ FEFFDAT_VALUES = ('reff', 'nleg', 'degen', 'rmass', 'rnorman',
38
+ 'gam_ch', 'rs_int', 'vint', 'vmu', 'vfermi')
39
+
40
+ FEFF_DB = None
41
+
42
+ @dataclass
43
+ class Group:
44
+ """Class for keeping track of an item in inventory."""
45
+ name: str
46
+ unit_price: float
47
+ quantity_on_hand: int = 0
48
+
49
+ def total_cost(self) -> float:
50
+ return self.unit_price * self.quantity_on_hand
51
+
52
+ def gfmt(x, n=10):
53
+ return gformat(x, length=n)
54
+
55
+ def etok(energy):
56
+ """convert photo-electron energy to wavenumber"""
57
+ if energy < 0: return 0
58
+ return np.sqrt(energy*ETOK)
59
+
60
+ def ktoe(k):
61
+ """convert photo-electron wavenumber to energy"""
62
+ return k*k*KTOE
63
+
64
+ def b64hash(val):
65
+ """return a base32 hash of a string"""
66
+ _hash = hashlib.sha256()
67
+ _hash.update(val.encode('utf-8'))
68
+ return b64encode(_hash.digest()).decode('utf-8')
69
+
70
+
71
+ def pathname_valid(fname):
72
+ if fname in ('', None):
73
+ return False
74
+ return (Path(fname).exists() or
75
+ (fname.startswith('feffdb[') and fname.endswith(']')))
76
+
77
+
78
+ class FeffDatFile:
79
+ def __init__(self, filename=None):
80
+ self.filename = filename
81
+ self.__reff__ = 0
82
+ self.__nleg__ = 0
83
+ self.__rmass = None
84
+ self.__geometry = None
85
+ if pathname_valid(filename):
86
+ self._read(filename)
87
+
88
+ def __repr__(self):
89
+ if self.filename is not None:
90
+ return f'FeffDatFile({self.filename})'
91
+ return 'FeffDatFile()'
92
+
93
+ def __copy__(self):
94
+ return FeffDatFile(filename=self.filename)
95
+
96
+ def __deepcopy__(self, memo):
97
+ return FeffDatFile(filename=self.filename)
98
+
99
+ @property
100
+ def reff(self):
101
+ return self.__reff__
102
+
103
+ @reff.setter
104
+ def reff(self, val):
105
+ pass
106
+
107
+ @property
108
+ def nleg(self):
109
+ return self.__nleg__
110
+
111
+ @nleg.setter
112
+ def nleg(self, val):
113
+ pass
114
+
115
+ @property
116
+ def rmass(self):
117
+ """reduced mass for a path"""
118
+ if self.__rmass is None and hasattr(self, 'geom'):
119
+ rmass = 0
120
+ for atsym, iz, ipot, amass, x, y, z in self.geom:
121
+ rmass += 1.0/max(1., amass)
122
+ self.__rmass = 1./rmass
123
+ return self.__rmass
124
+
125
+ @rmass.setter
126
+ def rmass(self, val):
127
+ pass
128
+
129
+ @property
130
+ def geometry(self):
131
+ """path geometry -- more complete than 'geom', closing matching `geometry`
132
+ from feffutils and get_feff_pathinfo, except without 'eta'
133
+ """
134
+ if not hasattr(self, '_geometry'):
135
+ self.__geometry = None
136
+ if not hasattr(self, 'geom'):
137
+ return None
138
+ if self.__geometry is None:
139
+ self.__geometry = []
140
+ pts, atoms = [], []
141
+ for atsym, iz, ipot, amass, x, y, z in self.geom:
142
+ pts.append(np.array([float(x), float(y), float(z)]))
143
+ atoms.append([atsym, ipot, float(x), float(y), float(z)])
144
+ pts.append(pts[0])
145
+ pts.append(pts[1])
146
+ atoms.append(atoms[0])
147
+ atoms.append(atoms[1])
148
+
149
+ for i in range(1, len(pts)-1):
150
+ p0 = pts[i-1]
151
+ p1 = pts[i+0]
152
+ p2 = pts[i+1]
153
+ a = p1 - p0
154
+ b = p2 - p1
155
+ dist = float(np.linalg.norm(pts[i]-pts[i-1]))
156
+ beta = float(np.acos(np.dot(a, b) / np.sqrt((np.dot(a, a) * np.dot(b, b))))*180/np.pi)
157
+ sym, ipot, x, y, z = atoms[i]
158
+ self.__geometry.append((sym, ipot, dist, x, y, z, beta, 0.0))
159
+ return self.__geometry
160
+
161
+ @geometry.setter
162
+ def geometry(self, val):
163
+ pass
164
+
165
+ def _set_from_dict(self, **kws):
166
+ self.__rmass = None
167
+ self.__geometry = None
168
+ for key, val in kws.items():
169
+ if key == 'rmass':
170
+ continue
171
+ elif key == 'reff':
172
+ key = '__reff__'
173
+ elif key == 'nleg':
174
+ key = '__nleg__'
175
+ elif key in FDAT_ARRS:
176
+ val = np.array(val)
177
+ setattr(self, key, val)
178
+
179
+ def __setstate__(self, state):
180
+ (self.filename, self.title, self.version, self.shell,
181
+ self.absorber, self.degen, self.__reff__, self.__nleg__,
182
+ self.rnorman, self.edge, self.gam_ch, self.exch, self.vmu, self.vfermi,
183
+ self.vint, self.rs_int, self.potentials, self.geom, self.__rmass,
184
+ self.k, self.real_phc, self.mag_feff, self.pha_feff,
185
+ self.red_fact, self.lam, self.rep, self.pha, self.amp) = state
186
+ self.k = np.array(self.k)
187
+ self.real_phc = np.array(self.real_phc)
188
+ self.mag_feff = np.array(self.mag_feff)
189
+ self.pha_feff = np.array(self.pha_feff)
190
+ self.red_fact = np.array(self.red_fact)
191
+ self.lam = np.array(self.lam)
192
+ self.rep = np.array(self.rep)
193
+ self.pha = np.array(self.pha)
194
+ self.amp = np.array(self.amp)
195
+
196
+ def __getstate__(self):
197
+ return (self.filename, self.title, self.version, self.shell,
198
+ self.absorber, self.degen, self.__reff__, self.__nleg__,
199
+ self.rnorman, self.edge, self.gam_ch, self.exch, self.vmu,
200
+ self.vfermi, self.vint, self.rs_int, self.potentials,
201
+ self.geom, self.__rmass, self.k.tolist(),
202
+ self.real_phc.tolist(), self.mag_feff.tolist(),
203
+ self.pha_feff.tolist(), self.red_fact.tolist(),
204
+ self.lam.tolist(), self.rep.tolist(), self.pha.tolist(),
205
+ self.amp.tolist())
206
+
207
+
208
+ def _read(self, filename):
209
+ if Path(filename).exists():
210
+ fefftext = read_textfile(filename)
211
+ elif (filename.startswith('feffdb[') and filename.endswith(']')):
212
+ global FEFF_DB
213
+ if FEFF_DB is None and FeffDatabase is not None:
214
+ FEFF_DB = FeffDatabase()
215
+ try:
216
+ fefftext = FEFF_DB.get_feffdat(filename[7:-1])
217
+ except Exception:
218
+ print(f"Error reading Feff Database '{filename}'")
219
+ return
220
+ if fefftext is None:
221
+ print(f"could not find entry in Feff Database '{filename}'")
222
+ return
223
+
224
+
225
+ self.filename = filename
226
+ mode = 'header'
227
+ self.potentials, self.geom = [], []
228
+ data = []
229
+ pcounter = 0
230
+ iline = 0
231
+ for line in fefftext.split('\n'):
232
+ iline += 1
233
+ line = line.strip()
234
+ if line.startswith('#'): line = line[1:]
235
+ line = line.strip()
236
+ if iline == 1:
237
+ self.title = line[:64].strip()
238
+ self.version = line[64:].strip()
239
+ continue
240
+ if line.startswith('k') and line.endswith('real[p]@#'):
241
+ mode = 'arrays'
242
+ continue
243
+ elif '----' in line[2:10]:
244
+ mode = 'path'
245
+ continue
246
+ #
247
+ if (mode == 'header' and
248
+ (re.match(r'^Abs\b', line) or re.match(r'^Pot\s+\d+\b', line)) and
249
+ re.search(r'\bZ\s*=', line)):
250
+ words = line.replace('=', ' ').split()
251
+ ipot, z, rmt, rnm = (0, 0, 0, 0)
252
+ words.pop(0)
253
+ if re.match(r'^Pot\s+\d+\b', line):
254
+ ipot = int(words.pop(0))
255
+ iz = int(words[1])
256
+ rmt = float(words[3])
257
+ rnm = float(words[5])
258
+ if re.match(r'^Abs\b', line):
259
+ self.shell = words[6]
260
+ self.potentials.append((ipot, iz, rmt, rnm))
261
+ elif mode == 'header' and re.match(r'^Gam_ch\s*=', line):
262
+ words = line.replace('=', ' ').split(None, 2)
263
+ self.gam_ch = float(words[1])
264
+ self.exch = words[2]
265
+ elif mode == 'header' and re.match(r'^Mu\s*=', line):
266
+ words = line.replace('=', ' ').replace('eV', ' ').split()
267
+ self.vmu = float(words[1])
268
+ self.vfermi = ktoe(float(words[3]))
269
+ self.vint = float(words[5])
270
+ self.rs_int= float(words[7])
271
+ elif mode == 'path':
272
+ pcounter += 1
273
+ if pcounter == 1:
274
+ w = [float(x) for x in line.split()[:5]]
275
+ self.__nleg__ = int(w.pop(0))
276
+ self.degen, self.__reff__, self.rnorman, self.edge = w
277
+ elif pcounter > 2:
278
+ words = line.split()
279
+ xyz = ["%8.5f" % float(x) for x in words[:3]]
280
+ ipot = int(words[3])
281
+ iz = int(words[4])
282
+ if len(words) > 5:
283
+ lab = words[5]
284
+ else:
285
+ lab = atomic_symbol(iz)
286
+ amass = atomic_mass(iz)
287
+ geom = [lab, iz, ipot, amass] + xyz
288
+ if len(self.geom) == 0:
289
+ self.absorber = lab
290
+ self.geom.append(tuple(geom))
291
+ elif mode == 'arrays':
292
+ d = np.array([float(x) for x in line.split()])
293
+ if len(d) == 7:
294
+ data.append(d)
295
+ data = np.array(data).transpose()
296
+ self.k = data[0]
297
+ self.real_phc = data[1]
298
+ self.mag_feff = data[2]
299
+ self.pha_feff = data[3]
300
+ self.red_fact = data[4]
301
+ self.lam = data[5]
302
+ self.rep = data[6]
303
+ self.pha = data[1] + data[3]
304
+ self.amp = data[2] * data[4]
305
+ self.__rmass = None # reduced mass of path
306
+ self.__geometry = None # path geometry list [atom, dist, angle]
307
+
308
+
309
+
310
+ class FeffPath():
311
+ """FeffPath object
312
+ has a FeffDatFile and can calculate EXAFS with its `calc_chi` method.
313
+ """
314
+ def __init__(self, filename=None, label='', feffrun='', s02=None, degen=None,
315
+ e0=None, ei=None, deltar=None, sigma2=None, third=None,
316
+ fourth=None):
317
+ self.filename = filename
318
+ self.feffrun = feffrun
319
+ self.label = label
320
+ self.params = None
321
+ self.spline_coefs = None
322
+ self.geom = []
323
+ self.shell = 'K'
324
+ self.absorber = None
325
+ self.dataset = None
326
+ self.hashkey = None
327
+ self.k = None
328
+ self.chi = None
329
+ self.use = True
330
+
331
+ self.degen = 1.0 if degen is None else degen
332
+ self.s02 = 1.0 if s02 is None else s02
333
+ self.e0 = 0.0 if e0 is None else e0
334
+ self.ei = 0.0 if ei is None else ei
335
+ self.deltar = 0.0 if deltar is None else deltar
336
+ self.sigma2 = 0.0 if sigma2 is None else sigma2
337
+ self.third = 0.0 if third is None else third
338
+ self.fourth = 0.0 if fourth is None else fourth
339
+
340
+ self._feffdat = None
341
+ if filename is not None:
342
+ self._get_feffdat(filename)
343
+
344
+ def _get_feffdat(self, filename):
345
+ if filename not in ('', None):
346
+ self._feffdat = FeffDatFile(filename=filename)
347
+ xpath = Path(filename).absolute()
348
+ self.filename = xpath.as_posix()
349
+ self.feffrun = xpath.parent.as_posix()
350
+
351
+ self.create_spline_coefs()
352
+ self.geom = self._feffdat.geom
353
+ self.shell = self._feffdat.shell
354
+ self.absorber = self._feffdat.absorber
355
+
356
+ self.hashkey = self.__geom2label()
357
+ if self.label in ('', None):
358
+ self.label = self.hashkey
359
+
360
+
361
+ def __getstate__(self):
362
+ _feffdat_state = self._feffdat.__getstate__()
363
+ return (self.filename, self.label, self.feffrun, self.degen,
364
+ self.s02, self.e0, self.ei, self.deltar, self.sigma2,
365
+ self.third, self.fourth, self.use, _feffdat_state)
366
+
367
+
368
+ def __setstate__(self, state):
369
+ self.params = self.spline_coefs = self.k = self.chi = None
370
+
371
+ (self.filename, self.label, self.feffrun, self.degen,
372
+ self.s02, self.e0, self.ei, self.deltar, self.sigma2,
373
+ self.third, self.fourth, self.use, _feffdat_state) = state
374
+
375
+ self._feffdat = FeffDatFile()
376
+ self._feffdat.__setstate__(_feffdat_state)
377
+ self.filename = Path(self.filename).absolute().as_posix()
378
+
379
+ self.create_spline_coefs()
380
+
381
+ self.geom = self._feffdat.geom
382
+ self.shell = self._feffdat.shell
383
+ self.absorber = self._feffdat.absorber
384
+
385
+ self.hashkey = self.__geom2label()
386
+ if self.label in ('', None):
387
+ self.label = self.hashkey
388
+
389
+
390
+ def __geom2label(self):
391
+ """generate label by hashing full filename and path geometry"""
392
+ rep = [self._feffdat.degen, self._feffdat.shell, self.filename]
393
+ for atom in self.geom:
394
+ rep.extend(atom)
395
+ rep.append("%7.4f" % self._feffdat.reff)
396
+ s = "|".join([str(i) for i in rep])
397
+ return "p%s" % (b64hash(s)[:8])
398
+
399
+ def pathpar_name(self, parname):
400
+ """
401
+ get internal name of lmfit Parameter for a path paramter,
402
+ using Path's hashkey
403
+ """
404
+ return f'{parname}_{self.dataset}_{self.hashkey}'
405
+
406
+ def __copy__(self):
407
+ newpath = FeffPath()
408
+ newpath.__setstate__(self.__getstate__())
409
+ return newpath
410
+
411
+ def __deepcopy__(self, memo):
412
+ newpath = FeffPath()
413
+ newpath.__setstate__(self.__getstate__())
414
+ return newpath
415
+
416
+ @property
417
+ def reff(self): return self._feffdat.reff
418
+
419
+ @reff.setter
420
+ def reff(self, val): pass
421
+
422
+ @property
423
+ def nleg(self): return self._feffdat.nleg
424
+
425
+ @nleg.setter
426
+ def nleg(self, val): pass
427
+
428
+ @property
429
+ def rmass(self): return self._feffdat.rmass
430
+
431
+ @rmass.setter
432
+ def rmass(self, val): pass
433
+
434
+
435
+ @property
436
+ def geometry(self):
437
+ return self._feffdat.geometry
438
+
439
+ @geometry.setter
440
+ def geometry(self, val):
441
+ pass
442
+
443
+ def __repr__(self):
444
+ return f"FeffPath(label='{self.label}', filename='{self.filename}')"
445
+
446
+ def create_spline_coefs(self):
447
+ """pre-calculate spline coefficients for feff data"""
448
+ self.spline_coefs = {}
449
+ fdat = self._feffdat
450
+ self.spline_coefs['pha'] = UnivariateSpline(fdat.k, fdat.pha, s=0)
451
+ self.spline_coefs['amp'] = UnivariateSpline(fdat.k, fdat.amp, s=0)
452
+ self.spline_coefs['rep'] = UnivariateSpline(fdat.k, fdat.rep, s=0)
453
+ self.spline_coefs['lam'] = UnivariateSpline(fdat.k, fdat.lam, s=0)
454
+
455
+ def calc_chi(self, k=None, kmax=None, kstep=None, degen=None, s02=None,
456
+ e0=None, ei=None, deltar=None, sigma2=None,
457
+ third=None, fourth=None, interp='cubic', **kws):
458
+ """calculate chi(k) with values provided for path parameters
459
+
460
+
461
+ """
462
+ fdat = self._feffdat
463
+
464
+ if degen is None: degen = self.degen
465
+ if s02 is None: s02 = self.s02
466
+ if e0 is None: e0 = self.e0
467
+ if ei is None: ei = self.ei
468
+ if deltar is None: deltar = self.deltar
469
+ if sigma2 is None: sigma2 = self.sigma2
470
+ if third is None: third = self.third
471
+ if fourth is None: fourth = self.fourth
472
+
473
+ if fdat.reff < 0.05:
474
+ print('reff is too small to calculate chi(k)')
475
+ return
476
+ # make sure we have a k array
477
+ if k is None:
478
+ if kmax is None:
479
+ kmax = 30.0
480
+ kmax = min(max(fdat.k), kmax)
481
+ if kstep is None:
482
+ kstep = 0.05
483
+ k = kstep * np.arange(int(1.01 + kmax/kstep), dtype='float64')
484
+
485
+ reff = fdat.reff
486
+ # get values for all the path parameters
487
+
488
+ # create e0-shifted energy and k, careful to look for |e0| ~= 0.
489
+ en = k*k - e0*ETOK
490
+ if min(abs(en)) < SMALL_ENERGY:
491
+ try:
492
+ en[np.where(abs(en) < 1.5*SMALL_ENERGY)] = SMALL_ENERGY
493
+ except ValueError:
494
+ pass
495
+ # q is the e0-shifted wavenumber
496
+ q = np.sign(en)*np.sqrt(abs(en))
497
+
498
+ # lookup Feff.dat values (pha, amp, rep, lam)
499
+ if interp.startswith('lin'):
500
+ pha = np.interp(q, fdat.k, fdat.pha)
501
+ amp = np.interp(q, fdat.k, fdat.amp)
502
+ rep = np.interp(q, fdat.k, fdat.rep)
503
+ lam = np.interp(q, fdat.k, fdat.lam)
504
+ else:
505
+ pha = self.spline_coefs['pha'](q)
506
+ amp = self.spline_coefs['amp'](q)
507
+ rep = self.spline_coefs['rep'](q)
508
+ lam = self.spline_coefs['lam'](q)
509
+
510
+ # p = complex wavenumber, and its square:
511
+ pp = (rep + 1j/lam)**2 + 1j * ei * ETOK
512
+ p = np.sqrt(pp)
513
+
514
+ # the xafs equation:
515
+ cchi = np.exp(-2*reff*p.imag - 2*pp*(sigma2 - pp*fourth/3) +
516
+ 1j*(2*q*reff + pha +
517
+ 2*p*(deltar - 2*sigma2/reff - 2*pp*third/3) ))
518
+
519
+ cchi = self.degen * s02 * amp * cchi / (q*(reff + deltar)**2)
520
+ cchi[0] = 2*cchi[1] - cchi[2]
521
+ # outputs:
522
+ self.k = k
523
+ self.p = p
524
+ self.chi = cchi.imag
525
+ self.chi_imag = -cchi.real
526
+
527
+
528
+
529
+ def feffpath(filename='', label='', feffrun='', s02=None, degen=None,
530
+ e0=None,ei=None, deltar=None, sigma2=None, third=None,
531
+ fourth=None, **kws):
532
+ """create a Feff Path Group from a *feffNNNN.dat* file.
533
+
534
+ Parameters:
535
+ -----------
536
+ filename: name (full path of) *feffNNNN.dat* file
537
+ label: label for path [file name]
538
+ degen: path degeneracy, N [taken from file]
539
+ s02: S_0^2 value or parameter [1.0]
540
+ e0: E_0 value or parameter [0.0]
541
+ deltar: delta_R value or parameter [0.0]
542
+ sigma2: sigma^2 value or parameter [0.0]
543
+ third: c_3 value or parameter [0.0]
544
+ fourth: c_4 value or parameter [0.0]
545
+ ei: E_i value or parameter [0.0]
546
+
547
+ For all the options described as **value or parameter** either a
548
+ numerical value or a Parameter (as created by param()) can be given.
549
+
550
+ Returns:
551
+ ---------
552
+ a FeffPath object.
553
+ """
554
+ if not pathname_valid(filename):
555
+ raise ValueError(f"Feff Path file '{filename:s}' not found")
556
+
557
+ return FeffPath(filename=filename, label=label, feffrun=feffrun,
558
+ s02=s02, degen=degen, e0=e0, ei=ei, deltar=deltar,
559
+ sigma2=sigma2, third=third, fourth=fourth)
feffdb/schema.py ADDED
@@ -0,0 +1,80 @@
1
+ VERSIONS = [(1, 'beta1', '2026-August-30', 'pre-release'),
2
+ ]
3
+
4
+ feffdb_schema = """
5
+
6
+ PRAGMA journal_mode=WAL;
7
+ PRAGMA foreign_keys=ON;
8
+ PRAGMA page_size=8192;
9
+
10
+ create table version (id integer primary key,
11
+ tag text not null,
12
+ date text not null,
13
+ notes text);
14
+
15
+ create table info (key text primary key not null, value text not null);
16
+
17
+ create table element (z integer primary key not null, symbol text not null,
18
+ name text not null, mass float not null);
19
+
20
+ create table cif (id integer primary key autoincrement,
21
+ ciftext blob not null,
22
+ space_group text not null,
23
+ formula text,
24
+ compound text,
25
+ source_db text,
26
+ source_id text);
27
+
28
+ create table feffinp (id integer primary key autoincrement,
29
+ inptext blob not null,
30
+ absorber integer not null,
31
+ edge text not null,
32
+ scatterers text not null,
33
+ natoms integer not null,
34
+ cif_id integer,
35
+ foreign key(cif_id) references cif(id),
36
+ foreign key(absorber) references element(z) );
37
+
38
+
39
+ create table feffdat (id integer primary key autoincrement,
40
+ absorber integer not null,
41
+ scatterer integer not null,
42
+ nleg integer not null,
43
+ reff float not null,
44
+ edge text not null,
45
+ degen integer not null,
46
+ geometry text not null,
47
+ label text,
48
+ description text,
49
+ feffinp_id integer,
50
+ feffdat blob not null,
51
+ donator_id integer default 1,
52
+ foreign key(feffinp_id) references feffinp(id),
53
+ foreign key(absorber) references element(z),
54
+ foreign key(scatterer) references element(z),
55
+ foreign key(donator_id) references person(id) );
56
+
57
+ create table person (id integer primary key autoincrement,
58
+ email text not null unique,
59
+ name text, password text);
60
+
61
+ insert into person (email, name) values ('feffdb@local', 'feffdb library');
62
+
63
+ create table rating_enum (scoreval integer primary key not null, notes text);
64
+
65
+ insert into rating_enum (scoreval) values (1);
66
+ insert into rating_enum (scoreval) values (2);
67
+ insert into rating_enum (scoreval) values (3);
68
+ insert into rating_enum (scoreval) values (4);
69
+ insert into rating_enum (scoreval) values (5);
70
+
71
+ create table feffdat_rating (id integer primary key autoincrement,
72
+ score integer not null,
73
+ review text,
74
+ feffdat_id integer not null,
75
+ person_id integer not null,
76
+ foreign key(score) references rating_enum(scoreval),
77
+ foreign key(feffdat_id) references feffdat(id),
78
+ foreign key(person_id) references person(id) );
79
+
80
+ """