philoch-bib-sdk 0.3.9__cp313-cp313-win_amd64.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.
- philoch_bib_sdk/__init__.py +0 -0
- philoch_bib_sdk/_rust.cp313-win_amd64.pyd +0 -0
- philoch_bib_sdk/adapters/io/__init__.py +115 -0
- philoch_bib_sdk/adapters/io/csv/__init__.py +308 -0
- philoch_bib_sdk/adapters/io/ods/__init__.py +145 -0
- philoch_bib_sdk/adapters/plaintext/bibitem_reader.py +0 -0
- philoch_bib_sdk/adapters/tabular_data/read_journal_volume_number_index.py +58 -0
- philoch_bib_sdk/converters/latex.py +6 -0
- philoch_bib_sdk/converters/plaintext/author/formatter.py +34 -0
- philoch_bib_sdk/converters/plaintext/author/parser.py +83 -0
- philoch_bib_sdk/converters/plaintext/bib_string_formatter.py +8 -0
- philoch_bib_sdk/converters/plaintext/bibitem/bibkey_formatter.py +21 -0
- philoch_bib_sdk/converters/plaintext/bibitem/bibkey_parser.py +158 -0
- philoch_bib_sdk/converters/plaintext/bibitem/date_formatter.py +37 -0
- philoch_bib_sdk/converters/plaintext/bibitem/date_parser.py +62 -0
- philoch_bib_sdk/converters/plaintext/bibitem/formatter.py +182 -0
- philoch_bib_sdk/converters/plaintext/bibitem/pages_formatter.py +13 -0
- philoch_bib_sdk/converters/plaintext/bibitem/pages_parser.py +63 -0
- philoch_bib_sdk/converters/plaintext/bibitem/parser.py +415 -0
- philoch_bib_sdk/converters/plaintext/journal/formatter.py +25 -0
- philoch_bib_sdk/converters/plaintext/journal/parser.py +36 -0
- philoch_bib_sdk/converters/plaintext/shared/renderable_formatter.py +25 -0
- philoch_bib_sdk/interfaces/cli/__init__.py +3 -0
- philoch_bib_sdk/interfaces/cli/fuzzy_matching.py +135 -0
- philoch_bib_sdk/logic/__init__.py +39 -0
- philoch_bib_sdk/logic/default_models.py +315 -0
- philoch_bib_sdk/logic/functions/__init__.py +31 -0
- philoch_bib_sdk/logic/functions/comparator.py +414 -0
- philoch_bib_sdk/logic/functions/fuzzy_matcher.py +796 -0
- philoch_bib_sdk/logic/functions/journal_article_matcher.py +44 -0
- philoch_bib_sdk/logic/literals.py +98 -0
- philoch_bib_sdk/logic/models.py +366 -0
- philoch_bib_sdk/logic/models_staging.py +173 -0
- philoch_bib_sdk/procedures/fuzzy_matching.py +112 -0
- philoch_bib_sdk/py.typed +0 -0
- philoch_bib_sdk/rust_scorer/Cargo.lock +232 -0
- philoch_bib_sdk/rust_scorer/Cargo.toml +26 -0
- philoch_bib_sdk/rust_scorer/pyproject.toml +15 -0
- philoch_bib_sdk/rust_scorer/rust_scorer.pyi +65 -0
- philoch_bib_sdk/rust_scorer/src/lib.rs +362 -0
- philoch_bib_sdk-0.3.9.dist-info/METADATA +15 -0
- philoch_bib_sdk-0.3.9.dist-info/RECORD +44 -0
- philoch_bib_sdk-0.3.9.dist-info/WHEEL +4 -0
- philoch_bib_sdk-0.3.9.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
use pyo3::prelude::*;
|
|
2
|
+
use rayon::prelude::*;
|
|
3
|
+
use std::cmp::Ordering;
|
|
4
|
+
use std::collections::BinaryHeap;
|
|
5
|
+
use strsim::jaro_winkler;
|
|
6
|
+
|
|
7
|
+
/// Normalize text: lowercase and collapse whitespace
|
|
8
|
+
fn normalize(s: &str) -> String {
|
|
9
|
+
s.to_lowercase()
|
|
10
|
+
.split_whitespace()
|
|
11
|
+
.collect::<Vec<_>>()
|
|
12
|
+
.join(" ")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Tokenize and sort tokens alphabetically
|
|
16
|
+
fn tokenize_and_sort(s: &str) -> Vec<&str> {
|
|
17
|
+
let mut tokens: Vec<&str> = s.split_whitespace().collect();
|
|
18
|
+
tokens.sort_unstable();
|
|
19
|
+
tokens
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Internal token sort ratio returning f64 (0.0-100.0)
|
|
23
|
+
fn token_sort_ratio_f64(s1: &str, s2: &str) -> f64 {
|
|
24
|
+
if s1.is_empty() || s2.is_empty() {
|
|
25
|
+
return 0.0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let norm1 = normalize(s1);
|
|
29
|
+
let norm2 = normalize(s2);
|
|
30
|
+
|
|
31
|
+
let sorted1 = tokenize_and_sort(&norm1).join(" ");
|
|
32
|
+
let sorted2 = tokenize_and_sort(&norm2).join(" ");
|
|
33
|
+
|
|
34
|
+
// Jaro-Winkler returns 0.0-1.0, scale to 0-100
|
|
35
|
+
jaro_winkler(&sorted1, &sorted2) * 100.0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Token sort ratio for Python: returns float 0.0-100.0
|
|
39
|
+
#[pyfunction]
|
|
40
|
+
fn token_sort_ratio(s1: &str, s2: &str) -> f64 {
|
|
41
|
+
token_sort_ratio_f64(s1, s2)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Input data for a single BibItem (simplified for Rust processing)
|
|
45
|
+
#[derive(Clone, Debug, FromPyObject)]
|
|
46
|
+
#[pyo3(from_item_all)]
|
|
47
|
+
struct BibItemData {
|
|
48
|
+
index: usize,
|
|
49
|
+
title: String,
|
|
50
|
+
author: String,
|
|
51
|
+
year: Option<i32>,
|
|
52
|
+
doi: Option<String>,
|
|
53
|
+
journal: Option<String>,
|
|
54
|
+
volume: Option<String>,
|
|
55
|
+
number: Option<String>,
|
|
56
|
+
pages: Option<String>,
|
|
57
|
+
publisher: Option<String>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Result of scoring a candidate against a subject
|
|
61
|
+
#[derive(Clone, Debug, IntoPyObject)]
|
|
62
|
+
struct MatchResult {
|
|
63
|
+
candidate_index: usize,
|
|
64
|
+
total_score: f64,
|
|
65
|
+
title_score: f64,
|
|
66
|
+
author_score: f64,
|
|
67
|
+
date_score: f64,
|
|
68
|
+
bonus_score: f64,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl PartialEq for MatchResult {
|
|
72
|
+
fn eq(&self, other: &Self) -> bool {
|
|
73
|
+
self.total_score == other.total_score
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl Eq for MatchResult {}
|
|
78
|
+
|
|
79
|
+
impl PartialOrd for MatchResult {
|
|
80
|
+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
81
|
+
Some(self.cmp(other))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl Ord for MatchResult {
|
|
86
|
+
fn cmp(&self, other: &Self) -> Ordering {
|
|
87
|
+
self.total_score
|
|
88
|
+
.partial_cmp(&other.total_score)
|
|
89
|
+
.unwrap_or(Ordering::Equal)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Score title similarity with bonuses
|
|
94
|
+
fn score_title(title1: &str, title2: &str, weight: f64) -> f64 {
|
|
95
|
+
if title1.is_empty() || title2.is_empty() {
|
|
96
|
+
return 0.0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let norm1 = normalize(title1);
|
|
100
|
+
let norm2 = normalize(title2);
|
|
101
|
+
|
|
102
|
+
let raw_score = token_sort_ratio_f64(&norm1, &norm2);
|
|
103
|
+
|
|
104
|
+
// Check if one title contains the other (subtitle handling)
|
|
105
|
+
let one_contains_other = norm1.contains(&norm2) || norm2.contains(&norm1);
|
|
106
|
+
|
|
107
|
+
// Check for undesired keywords mismatch
|
|
108
|
+
let undesired = ["errata", "review"];
|
|
109
|
+
let has_undesired1: Vec<_> = undesired.iter().filter(|kw| norm1.contains(*kw)).collect();
|
|
110
|
+
let has_undesired2: Vec<_> = undesired.iter().filter(|kw| norm2.contains(*kw)).collect();
|
|
111
|
+
let kw_mismatch = has_undesired1.len() != has_undesired2.len()
|
|
112
|
+
|| has_undesired1.iter().any(|kw| !has_undesired2.contains(kw));
|
|
113
|
+
|
|
114
|
+
let mut final_score = raw_score;
|
|
115
|
+
|
|
116
|
+
// High similarity bonus
|
|
117
|
+
if (raw_score > 85.0 || one_contains_other) && !kw_mismatch {
|
|
118
|
+
final_score += 100.0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Penalty for keyword mismatch
|
|
122
|
+
if kw_mismatch {
|
|
123
|
+
// penalty_count is at most 2 (size of undesired array), safe to multiply directly
|
|
124
|
+
let penalty_count = has_undesired1.len().abs_diff(has_undesired2.len());
|
|
125
|
+
final_score -= (penalty_count * 50) as f64;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
final_score.max(0.0) * weight
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Score author similarity with bonuses
|
|
132
|
+
fn score_author(author1: &str, author2: &str, weight: f64) -> f64 {
|
|
133
|
+
if author1.is_empty() || author2.is_empty() {
|
|
134
|
+
return 0.0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let raw_score = token_sort_ratio_f64(author1, author2);
|
|
138
|
+
let mut final_score = raw_score;
|
|
139
|
+
|
|
140
|
+
if raw_score > 85.0 {
|
|
141
|
+
final_score += 100.0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
final_score * weight
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Score date similarity
|
|
148
|
+
fn score_date(year1: Option<i32>, year2: Option<i32>, weight: f64) -> f64 {
|
|
149
|
+
match (year1, year2) {
|
|
150
|
+
(Some(y1), Some(y2)) => {
|
|
151
|
+
let diff = y1.abs_diff(y2);
|
|
152
|
+
let score = match diff {
|
|
153
|
+
0 => 100.0,
|
|
154
|
+
1..=3 => 100.0 - f64::from(diff) * 10.0,
|
|
155
|
+
_ if y1 / 10 == y2 / 10 => 30.0, // Same decade
|
|
156
|
+
_ => 0.0,
|
|
157
|
+
};
|
|
158
|
+
score * weight
|
|
159
|
+
}
|
|
160
|
+
_ => 0.0,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Score bonus fields (DOI, journal+vol+num, pages, publisher)
|
|
165
|
+
fn score_bonus(subject: &BibItemData, candidate: &BibItemData, weight: f64) -> f64 {
|
|
166
|
+
let mut bonus = 0.0;
|
|
167
|
+
|
|
168
|
+
// DOI exact match (highest confidence)
|
|
169
|
+
if let (Some(ref doi1), Some(ref doi2)) = (&subject.doi, &candidate.doi) {
|
|
170
|
+
if !doi1.is_empty() && doi1 == doi2 {
|
|
171
|
+
bonus += 100.0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Journal + Volume + Number match
|
|
176
|
+
if let (Some(ref j1), Some(ref j2)) = (&subject.journal, &candidate.journal) {
|
|
177
|
+
let norm_j1 = normalize(j1);
|
|
178
|
+
let norm_j2 = normalize(j2);
|
|
179
|
+
if !norm_j1.is_empty() && norm_j1 == norm_j2 {
|
|
180
|
+
let vol_match = match (&subject.volume, &candidate.volume) {
|
|
181
|
+
(Some(v1), Some(v2)) => !v1.is_empty() && v1 == v2,
|
|
182
|
+
_ => false,
|
|
183
|
+
};
|
|
184
|
+
let num_match = match (&subject.number, &candidate.number) {
|
|
185
|
+
(Some(n1), Some(n2)) => !n1.is_empty() && n1 == n2,
|
|
186
|
+
_ => false,
|
|
187
|
+
};
|
|
188
|
+
if vol_match && num_match {
|
|
189
|
+
bonus += 50.0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Pages match
|
|
195
|
+
if let (Some(ref p1), Some(ref p2)) = (&subject.pages, &candidate.pages) {
|
|
196
|
+
if !p1.is_empty() && p1 == p2 {
|
|
197
|
+
bonus += 20.0;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Publisher match
|
|
202
|
+
if let (Some(ref pub1), Some(ref pub2)) = (&subject.publisher, &candidate.publisher) {
|
|
203
|
+
if !pub1.is_empty() && !pub2.is_empty() {
|
|
204
|
+
let pub_score = token_sort_ratio_f64(pub1, pub2);
|
|
205
|
+
if pub_score > 85.0 {
|
|
206
|
+
bonus += 10.0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
bonus * weight
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Score a single candidate against a subject
|
|
215
|
+
fn score_candidate(subject: &BibItemData, candidate: &BibItemData) -> MatchResult {
|
|
216
|
+
// Weights: title=0.5, author=0.3, date=0.1, bonus=0.1
|
|
217
|
+
let title_score = score_title(&subject.title, &candidate.title, 0.5);
|
|
218
|
+
let author_score = score_author(&subject.author, &candidate.author, 0.3);
|
|
219
|
+
let date_score = score_date(subject.year, candidate.year, 0.1);
|
|
220
|
+
let bonus_score = score_bonus(subject, candidate, 0.1);
|
|
221
|
+
|
|
222
|
+
let total_score = title_score + author_score + date_score + bonus_score;
|
|
223
|
+
|
|
224
|
+
MatchResult {
|
|
225
|
+
candidate_index: candidate.index,
|
|
226
|
+
total_score,
|
|
227
|
+
title_score,
|
|
228
|
+
author_score,
|
|
229
|
+
date_score,
|
|
230
|
+
bonus_score,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// Find top N matches for a single subject
|
|
235
|
+
fn find_top_matches(
|
|
236
|
+
subject: &BibItemData,
|
|
237
|
+
candidates: &[BibItemData],
|
|
238
|
+
top_n: usize,
|
|
239
|
+
min_score: f64,
|
|
240
|
+
) -> Vec<MatchResult> {
|
|
241
|
+
// Quick DOI check first
|
|
242
|
+
if let Some(ref subject_doi) = subject.doi {
|
|
243
|
+
if !subject_doi.is_empty() {
|
|
244
|
+
for candidate in candidates {
|
|
245
|
+
if let Some(ref cand_doi) = candidate.doi {
|
|
246
|
+
if subject_doi == cand_doi {
|
|
247
|
+
return vec![score_candidate(subject, candidate)];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Score all candidates and keep top N
|
|
255
|
+
let mut heap: BinaryHeap<MatchResult> = BinaryHeap::new();
|
|
256
|
+
|
|
257
|
+
for candidate in candidates {
|
|
258
|
+
let result = score_candidate(subject, candidate);
|
|
259
|
+
if result.total_score >= min_score {
|
|
260
|
+
heap.push(result);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Extract top N
|
|
265
|
+
let mut results: Vec<MatchResult> = Vec::with_capacity(top_n.min(heap.len()));
|
|
266
|
+
for _ in 0..top_n {
|
|
267
|
+
if let Some(result) = heap.pop() {
|
|
268
|
+
results.push(result);
|
|
269
|
+
} else {
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
results
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// Result for a single subject with its top matches
|
|
278
|
+
#[derive(Clone, Debug, IntoPyObject)]
|
|
279
|
+
struct SubjectMatchResult {
|
|
280
|
+
subject_index: usize,
|
|
281
|
+
matches: Vec<MatchResult>,
|
|
282
|
+
candidates_searched: usize,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Batch score multiple subjects against candidates in parallel.
|
|
286
|
+
#[pyfunction]
|
|
287
|
+
fn score_batch(
|
|
288
|
+
subjects: Vec<BibItemData>,
|
|
289
|
+
candidates: Vec<BibItemData>,
|
|
290
|
+
top_n: usize,
|
|
291
|
+
min_score: f64,
|
|
292
|
+
) -> Vec<SubjectMatchResult> {
|
|
293
|
+
let candidates_len = candidates.len();
|
|
294
|
+
|
|
295
|
+
subjects
|
|
296
|
+
.par_iter()
|
|
297
|
+
.enumerate()
|
|
298
|
+
.map(|(idx, subject)| {
|
|
299
|
+
let matches = find_top_matches(subject, &candidates, top_n, min_score);
|
|
300
|
+
SubjectMatchResult {
|
|
301
|
+
subject_index: idx,
|
|
302
|
+
matches,
|
|
303
|
+
candidates_searched: candidates_len,
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
.collect()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// A Python module implemented in Rust for fast fuzzy matching.
|
|
310
|
+
#[pymodule]
|
|
311
|
+
fn rust_scorer(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
312
|
+
m.add_function(wrap_pyfunction!(token_sort_ratio, m)?)?;
|
|
313
|
+
m.add_function(wrap_pyfunction!(score_batch, m)?)?;
|
|
314
|
+
Ok(())
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#[cfg(test)]
|
|
318
|
+
mod tests {
|
|
319
|
+
use super::*;
|
|
320
|
+
|
|
321
|
+
#[test]
|
|
322
|
+
fn test_token_sort_ratio_identical() {
|
|
323
|
+
let score = token_sort_ratio("hello world", "hello world");
|
|
324
|
+
assert!((score - 100.0).abs() < 0.001);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[test]
|
|
328
|
+
fn test_token_sort_ratio_reordered() {
|
|
329
|
+
let score = token_sort_ratio("hello world", "world hello");
|
|
330
|
+
assert!((score - 100.0).abs() < 0.001);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#[test]
|
|
334
|
+
fn test_token_sort_ratio_different() {
|
|
335
|
+
let score = token_sort_ratio("hello world", "goodbye moon");
|
|
336
|
+
assert!(score < 50.0);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[test]
|
|
340
|
+
fn test_token_sort_ratio_empty() {
|
|
341
|
+
assert!((token_sort_ratio("", "hello") - 0.0).abs() < 0.001);
|
|
342
|
+
assert!((token_sort_ratio("hello", "") - 0.0).abs() < 0.001);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#[test]
|
|
346
|
+
fn test_score_date_exact() {
|
|
347
|
+
let score = score_date(Some(2020), Some(2020), 1.0);
|
|
348
|
+
assert!((score - 100.0).abs() < 0.001);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#[test]
|
|
352
|
+
fn test_score_date_close() {
|
|
353
|
+
let score = score_date(Some(2020), Some(2021), 1.0);
|
|
354
|
+
assert!((score - 90.0).abs() < 0.001);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#[test]
|
|
358
|
+
fn test_score_date_same_decade() {
|
|
359
|
+
let score = score_date(Some(2020), Some(2025), 1.0);
|
|
360
|
+
assert!((score - 30.0).abs() < 0.001);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: philoch-bib-sdk
|
|
3
|
+
Version: 0.3.9
|
|
4
|
+
License-File: LICENSE
|
|
5
|
+
Summary: Standard development kit for the Philosophie Bibliography project
|
|
6
|
+
Author-email: Luis Alejandro Bordo García <luis.bordo@philosophie.ch>
|
|
7
|
+
Maintainer-email: Luis Alejandro Bordo García <tech@philosophie.ch>
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
11
|
+
|
|
12
|
+
# Philosophie.ch Bibliography SDK
|
|
13
|
+
|
|
14
|
+
This repository contains the standard development kit for the bibliography project of the [Philosophie.ch](https://philosophie.ch) association.
|
|
15
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
philoch_bib_sdk\__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
philoch_bib_sdk\_rust.cp313-win_amd64.pyd,sha256=CmkHcTY4RY9NBg-mNFN9NoaMvKTs51voH52AdjK26T8,286720
|
|
3
|
+
philoch_bib_sdk\adapters\io\__init__.py,sha256=Xxar4MA-37fBEd42PIlYmpA5G3WXmJ0JqKFAOoLnnhM,3466
|
|
4
|
+
philoch_bib_sdk\adapters\io\csv\__init__.py,sha256=YEU0h9VE4V91bmUJ45KTzJVplKp3IwGWaAAm_T6JKSo,10485
|
|
5
|
+
philoch_bib_sdk\adapters\io\ods\__init__.py,sha256=MTVnjP_hPI8hEibv7CYLD06gUMc_G1BPkvWqOvKLPD8,4874
|
|
6
|
+
philoch_bib_sdk\adapters\plaintext\bibitem_reader.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
philoch_bib_sdk\adapters\tabular_data\read_journal_volume_number_index.py,sha256=GLfBTilBm2dzzPBk0OEAi3q9rMO0O8umdzlfMNqX0_8,1805
|
|
8
|
+
philoch_bib_sdk\converters\latex.py,sha256=LuAKLrClECuBeaDQYJc7tIJECEV4h0kt0VE_ssv3s0o,236
|
|
9
|
+
philoch_bib_sdk\converters\plaintext\author\formatter.py,sha256=qoYKakj5BzJcTUI1XnANZcx70kqLq6KsY84C46RuAU4,1032
|
|
10
|
+
philoch_bib_sdk\converters\plaintext\author\parser.py,sha256=i2p7bsHR9yN9aW-zR0R5B5-9VIh3_9b_zEvUb_WfeAU,2866
|
|
11
|
+
philoch_bib_sdk\converters\plaintext\bib_string_formatter.py,sha256=5Z97u5GryHUgZcPhWE41thgWCB4wYu22pZ9et6nakmw,329
|
|
12
|
+
philoch_bib_sdk\converters\plaintext\bibitem\bibkey_formatter.py,sha256=YivsY0gblKJdC4yKYZ3tvWmKIvFXW4iNht9zhz8oFUs,565
|
|
13
|
+
philoch_bib_sdk\converters\plaintext\bibitem\bibkey_parser.py,sha256=TKHFQ9QeZ2Jgm3sFCjTqz_PDfous0amvz3DB0AJA51E,4991
|
|
14
|
+
philoch_bib_sdk\converters\plaintext\bibitem\date_formatter.py,sha256=G2mbaJidDg8avKBbro1rVcEznPC92XVTDQ4fSdmvhJo,1480
|
|
15
|
+
philoch_bib_sdk\converters\plaintext\bibitem\date_parser.py,sha256=3ZYGhhGqILzrvnwOvG4NPAjErLwVva0dfsN0B9eFomg,2242
|
|
16
|
+
philoch_bib_sdk\converters\plaintext\bibitem\formatter.py,sha256=EjSwHYAPn0YRjeLGK_rCi26Wtug6X5x5DFEKPjStn30,6298
|
|
17
|
+
philoch_bib_sdk\converters\plaintext\bibitem\pages_formatter.py,sha256=punzwm8ObrLJhsCOS1oKHSnTXMX_R_0Xs9M866J44pU,397
|
|
18
|
+
philoch_bib_sdk\converters\plaintext\bibitem\pages_parser.py,sha256=mMFviMZo5qHs0K_SXfbmjJ_nbmTGnSiKMrXyazzO2Qs,2018
|
|
19
|
+
philoch_bib_sdk\converters\plaintext\bibitem\parser.py,sha256=DakeRU4Dj8G1XaMf_ukJGUtgobLEoiUzg_eHVPcNORs,13536
|
|
20
|
+
philoch_bib_sdk\converters\plaintext\journal\formatter.py,sha256=o5ikU-aNFr6cxgzD0rBCjymHLpGrD6RGvNE8V2sX52s,599
|
|
21
|
+
philoch_bib_sdk\converters\plaintext\journal\parser.py,sha256=kT1YHwc9Am82WHRhaSWXaCeKitPn9QLWIbmIe8T1of4,1092
|
|
22
|
+
philoch_bib_sdk\converters\plaintext\shared\renderable_formatter.py,sha256=oS5u8RJpkRXaDTmauVqZi-uuXsyG-UQZMK2pgzSk-qo,686
|
|
23
|
+
philoch_bib_sdk\interfaces\cli\__init__.py,sha256=rKjz91BZYYu9suJyFMeS7GkBxaM1KEhL2G7EZj8JXdE,70
|
|
24
|
+
philoch_bib_sdk\interfaces\cli\fuzzy_matching.py,sha256=4OLbcHd9HZAiDzOxnjfmRwP3BTCz_UIGw6SlBO4Xwa4,3809
|
|
25
|
+
philoch_bib_sdk\logic\__init__.py,sha256=avTEfQfhr77hhsujj12GZ9MvTGZy2-DIQ1ncXTdGLFA,709
|
|
26
|
+
philoch_bib_sdk\logic\default_models.py,sha256=cHHKSFmNR29qBxQkPwelQ09sx66isHlAIr1PiIHAvH4,10467
|
|
27
|
+
philoch_bib_sdk\logic\functions\__init__.py,sha256=U2qNkMGxeFK-kBQYhD8kO6J2BpmjQPszOom3X_KvY7M,796
|
|
28
|
+
philoch_bib_sdk\logic\functions\comparator.py,sha256=enwy5-zkIDc5cXzp6eWvaz4LcVYDqxENlW-jRJ44IQI,13738
|
|
29
|
+
philoch_bib_sdk\logic\functions\fuzzy_matcher.py,sha256=JBdjj1soFhzomqir0y6bohkIcLS9DDSYnr5cuZ_LAhs,26483
|
|
30
|
+
philoch_bib_sdk\logic\functions\journal_article_matcher.py,sha256=Twv_UCRCMHEHyroG29BQjvkq_SHM60rjynfIywqCS5E,1330
|
|
31
|
+
philoch_bib_sdk\logic\literals.py,sha256=RLzpN3pJu0XZhkEUpMObr6ql-BdpMtpTcWeNw6aYBP4,1901
|
|
32
|
+
philoch_bib_sdk\logic\models.py,sha256=xHCQWFq_rEcX967icALD4oOQjM8AlLKLzXQ8SP-YNis,8681
|
|
33
|
+
philoch_bib_sdk\logic\models_staging.py,sha256=GdWT7IR4VGn9d3n2fcEWqGSjgHI5eLsbxC2zkIwj8ow,5880
|
|
34
|
+
philoch_bib_sdk\procedures\fuzzy_matching.py,sha256=J025vIaAHbwuQKzteGBgsM65VcRDeCNCzNrlqwFdKRE,4361
|
|
35
|
+
philoch_bib_sdk\py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
+
philoch_bib_sdk\rust_scorer\Cargo.lock,sha256=lXUfxNTAmUwdY8pORdeYPBvb9gKaTTutvBd57WKZG1o,6076
|
|
37
|
+
philoch_bib_sdk\rust_scorer\Cargo.toml,sha256=CsQ_LVSV34ZehikG7Z6nzQIzH9Kjcz1GBXB8YM34Cg0,443
|
|
38
|
+
philoch_bib_sdk\rust_scorer\pyproject.toml,sha256=91LsDiXEU49-Fy5JYKNf6JyWTMmtBcn_zRw3h2y1KAA,391
|
|
39
|
+
philoch_bib_sdk\rust_scorer\rust_scorer.pyi,sha256=CDT-4bpH9pOlEkxaqbVOzrZTiTIFzy5dsn8rqRT23Rs,1623
|
|
40
|
+
philoch_bib_sdk\rust_scorer\src\lib.rs,sha256=btLd9hhNdVVTfpVIrSpD9safSKp9AU7d8I5dgV6oGds,10377
|
|
41
|
+
philoch_bib_sdk-0.3.9.dist-info\METADATA,sha256=MKqlxSvctbz9Buvma3Ia0cDoYTzA5FUybhvn85BV73k,582
|
|
42
|
+
philoch_bib_sdk-0.3.9.dist-info\WHEEL,sha256=n_BmF69IyGtioVWE9c3M_zsEfe6-xMZy1v5HCL_6qE0,97
|
|
43
|
+
philoch_bib_sdk-0.3.9.dist-info\licenses\LICENSE,sha256=nplGobji9gkYmJxDBbBz2SKjZY27SUaqhqKkpUB-C30,1070
|
|
44
|
+
philoch_bib_sdk-0.3.9.dist-info\RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Philosophie.ch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|