vomebook commited on
Commit
ac10e6a
·
verified ·
1 Parent(s): 3ced431

Upload 3 files

Browse files
Files changed (3) hide show
  1. app/data_loader.py +50 -0
  2. app/indexer.py +4 -0
  3. app/main.py +37 -3
app/data_loader.py CHANGED
@@ -47,6 +47,49 @@ def text_from_article(article: dict[str, Any]) -> str:
47
  return "\n".join(values)
48
 
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  def iter_documents() -> Iterator[dict[str, Any]]:
51
  for archive_id in range(ARCHIVE_START, ARCHIVE_END + 1):
52
  archive_root = PARSED_ROOT / f"archives{archive_id}"
@@ -65,9 +108,11 @@ def iter_documents() -> Iterator[dict[str, Any]]:
65
  continue
66
  tags_path = article_path.with_suffix(".tags")
67
  tags = read_json(tags_path) if tags_path.exists() else []
 
68
  authors = article.get("authors") or []
69
  if not isinstance(authors, list):
70
  authors = []
 
71
  doc_id = f"{article_id}--{publication_id}"
72
  yield {
73
  "doc_id": doc_id,
@@ -81,7 +126,12 @@ def iter_documents() -> Iterator[dict[str, Any]]:
81
  "title": str(article.get("title") or article_id),
82
  "authors": [str(item) for item in authors],
83
  "dates": article.get("dates") or [],
 
 
 
84
  "tags": tags if isinstance(tags, list) else [],
 
 
85
  "content": text_from_article(article),
86
  "path": str(article_path.relative_to(archive_root)),
87
  }
 
47
  return "\n".join(values)
48
 
49
 
50
+ def normalize_date_value(item: dict[str, Any]) -> int | None:
51
+ year = item.get("year")
52
+ if not isinstance(year, int):
53
+ return None
54
+ month = item.get("month") if isinstance(item.get("month"), int) else 1
55
+ day = item.get("day") if isinstance(item.get("day"), int) else 1
56
+ return year * 10000 + month * 100 + day
57
+
58
+
59
+ def normalize_dates(article: dict[str, Any]) -> tuple[int | None, int | None, list[str]]:
60
+ raw_dates = article.get("dates") if isinstance(article.get("dates"), list) else []
61
+ values: list[int] = []
62
+ displays: list[str] = []
63
+ for item in raw_dates:
64
+ if not isinstance(item, dict):
65
+ continue
66
+ value = normalize_date_value(item)
67
+ if value is not None:
68
+ values.append(value)
69
+ year = item.get("year") if item.get("year") is not None else "----"
70
+ month = item.get("month") if item.get("month") is not None else "--"
71
+ day = item.get("day") if item.get("day") is not None else "--"
72
+ displays.append(f"{year}/{month}/{day}")
73
+ return (min(values) if values else None, max(values) if values else None, displays)
74
+
75
+
76
+ def normalize_tags(tags: Any) -> tuple[list[str], list[str]]:
77
+ if not isinstance(tags, list):
78
+ return [], []
79
+ names: list[str] = []
80
+ types: list[str] = []
81
+ for item in tags:
82
+ if not isinstance(item, dict):
83
+ continue
84
+ name = item.get("name")
85
+ tag_type = item.get("type")
86
+ if name is not None:
87
+ names.append(str(name))
88
+ if tag_type is not None:
89
+ types.append(str(tag_type))
90
+ return names, types
91
+
92
+
93
  def iter_documents() -> Iterator[dict[str, Any]]:
94
  for archive_id in range(ARCHIVE_START, ARCHIVE_END + 1):
95
  archive_root = PARSED_ROOT / f"archives{archive_id}"
 
108
  continue
109
  tags_path = article_path.with_suffix(".tags")
110
  tags = read_json(tags_path) if tags_path.exists() else []
111
+ tag_names, tag_types = normalize_tags(tags)
112
  authors = article.get("authors") or []
113
  if not isinstance(authors, list):
114
  authors = []
115
+ date_min, date_max, date_display = normalize_dates(article)
116
  doc_id = f"{article_id}--{publication_id}"
117
  yield {
118
  "doc_id": doc_id,
 
126
  "title": str(article.get("title") or article_id),
127
  "authors": [str(item) for item in authors],
128
  "dates": article.get("dates") or [],
129
+ "date_min": date_min,
130
+ "date_max": date_max,
131
+ "date_display": date_display,
132
  "tags": tags if isinstance(tags, list) else [],
133
+ "tag_names": tag_names,
134
+ "tag_types": tag_types,
135
  "content": text_from_article(article),
136
  "path": str(article_path.relative_to(archive_root)),
137
  }
app/indexer.py CHANGED
@@ -34,6 +34,10 @@ def index_mapping() -> dict:
34
  "archive_id": {"type": "integer"},
35
  "title": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
36
  "authors": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
 
 
 
 
37
  "content": {"type": "text", "analyzer": "text_mixed"},
38
  "path": {"type": "keyword"},
39
  }
 
34
  "archive_id": {"type": "integer"},
35
  "title": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
36
  "authors": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
37
+ "tag_names": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
38
+ "tag_types": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
39
+ "date_min": {"type": "integer"},
40
+ "date_max": {"type": "integer"},
41
  "content": {"type": "text", "analyzer": "text_mixed"},
42
  "path": {"type": "keyword"},
43
  }
app/main.py CHANGED
@@ -25,6 +25,12 @@ class SearchRequest(BaseModel):
25
  page_size: int = 20
26
  exact: bool = True
27
  source: str | None = None
 
 
 
 
 
 
28
 
29
 
30
  def has_wildcard(query: str) -> bool:
@@ -36,6 +42,18 @@ def build_query(body: SearchRequest) -> dict[str, Any]:
36
  filters: list[dict[str, Any]] = []
37
  if body.source:
38
  filters.append({"term": {"publication_name.keyword": body.source}})
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  if not q:
41
  query: dict[str, Any] = {"match_all": {}}
@@ -78,6 +96,8 @@ def trim_hit(hit: dict[str, Any]) -> dict[str, Any]:
78
  "publication_name": source.get("publication_name"),
79
  "archive_id": source.get("archive_id"),
80
  "publication_type": source.get("publication_type"),
 
 
81
  "title": source.get("title"),
82
  "authors": source.get("authors") or [],
83
  "path": source.get("path"),
@@ -96,10 +116,22 @@ def sources():
96
  data = es.search(
97
  index=INDEX_NAME,
98
  size=0,
99
- aggs={"sources": {"terms": {"field": "publication_name.keyword", "size": 500}}},
 
 
 
 
 
 
100
  )
101
- buckets = data.get("aggregations", {}).get("sources", {}).get("buckets", [])
102
- return [{"name": item["key"], "count": item["doc_count"]} for item in buckets]
 
 
 
 
 
 
103
 
104
 
105
  @app.post("/api/search")
@@ -129,6 +161,8 @@ def preview(doc_id: str):
129
  "title": source.get("title"),
130
  "publication_name": source.get("publication_name"),
131
  "publication_type": source.get("publication_type"),
 
 
132
  "authors": source.get("authors") or [],
133
  "content": source.get("content") or "",
134
  "article_id": source.get("article_id"),
 
25
  page_size: int = 20
26
  exact: bool = True
27
  source: str | None = None
28
+ author: str | None = None
29
+ tag: str | None = None
30
+ archive_id: int | None = None
31
+ publication_type: str | None = None
32
+ year_from: int | None = None
33
+ year_to: int | None = None
34
 
35
 
36
  def has_wildcard(query: str) -> bool:
 
42
  filters: list[dict[str, Any]] = []
43
  if body.source:
44
  filters.append({"term": {"publication_name.keyword": body.source}})
45
+ if body.author:
46
+ filters.append({"term": {"authors.keyword": body.author}})
47
+ if body.tag:
48
+ filters.append({"term": {"tag_names.keyword": body.tag}})
49
+ if body.archive_id is not None:
50
+ filters.append({"term": {"archive_id": body.archive_id}})
51
+ if body.publication_type:
52
+ filters.append({"term": {"publication_type": body.publication_type}})
53
+ if body.year_from is not None:
54
+ filters.append({"range": {"date_max": {"gte": body.year_from * 10000}}})
55
+ if body.year_to is not None:
56
+ filters.append({"range": {"date_min": {"lte": body.year_to * 10000 + 1231}}})
57
 
58
  if not q:
59
  query: dict[str, Any] = {"match_all": {}}
 
96
  "publication_name": source.get("publication_name"),
97
  "archive_id": source.get("archive_id"),
98
  "publication_type": source.get("publication_type"),
99
+ "date_display": source.get("date_display") or [],
100
+ "tag_names": source.get("tag_names") or [],
101
  "title": source.get("title"),
102
  "authors": source.get("authors") or [],
103
  "path": source.get("path"),
 
116
  data = es.search(
117
  index=INDEX_NAME,
118
  size=0,
119
+ aggs={
120
+ "sources": {"terms": {"field": "publication_name.keyword", "size": 500}},
121
+ "authors": {"terms": {"field": "authors.keyword", "size": 300}},
122
+ "tags": {"terms": {"field": "tag_names.keyword", "size": 300}},
123
+ "archives": {"terms": {"field": "archive_id", "size": 64}},
124
+ "types": {"terms": {"field": "publication_type", "size": 32}},
125
+ },
126
  )
127
+ aggs = data.get("aggregations", {})
128
+ return {
129
+ "sources": [{"name": item["key"], "count": item["doc_count"]} for item in aggs.get("sources", {}).get("buckets", [])],
130
+ "authors": [{"name": item["key"], "count": item["doc_count"]} for item in aggs.get("authors", {}).get("buckets", [])],
131
+ "tags": [{"name": item["key"], "count": item["doc_count"]} for item in aggs.get("tags", {}).get("buckets", [])],
132
+ "archives": [{"name": str(item["key"]), "value": item["key"], "count": item["doc_count"]} for item in aggs.get("archives", {}).get("buckets", [])],
133
+ "types": [{"name": item["key"], "count": item["doc_count"]} for item in aggs.get("types", {}).get("buckets", [])],
134
+ }
135
 
136
 
137
  @app.post("/api/search")
 
161
  "title": source.get("title"),
162
  "publication_name": source.get("publication_name"),
163
  "publication_type": source.get("publication_type"),
164
+ "date_display": source.get("date_display") or [],
165
+ "tag_names": source.get("tag_names") or [],
166
  "authors": source.get("authors") or [],
167
  "content": source.get("content") or "",
168
  "article_id": source.get("article_id"),