pub-analyzer 0.1.2__py3-none-any.whl → 0.3.0__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.

Potentially problematic release.


This version of pub-analyzer might be problematic. Click here for more details.

Files changed (53) hide show
  1. pub_analyzer/css/body.tcss +48 -35
  2. pub_analyzer/css/buttons.tcss +0 -1
  3. pub_analyzer/css/collapsible.tcss +31 -0
  4. pub_analyzer/css/main.tcss +4 -0
  5. pub_analyzer/css/summary.tcss +75 -0
  6. pub_analyzer/internal/identifier.py +36 -10
  7. pub_analyzer/internal/render.py +1 -1
  8. pub_analyzer/internal/report.py +177 -53
  9. pub_analyzer/internal/templates/author/{author_resume.typ → author_summary.typ} +4 -3
  10. pub_analyzer/internal/templates/author/report.typ +4 -3
  11. pub_analyzer/internal/templates/author/sources.typ +7 -5
  12. pub_analyzer/internal/templates/author/works.typ +12 -12
  13. pub_analyzer/internal/templates/author/works_extended.typ +4 -4
  14. pub_analyzer/main.py +6 -7
  15. pub_analyzer/models/author.py +20 -28
  16. pub_analyzer/models/concept.py +19 -0
  17. pub_analyzer/models/institution.py +22 -5
  18. pub_analyzer/models/report.py +14 -14
  19. pub_analyzer/models/source.py +59 -3
  20. pub_analyzer/models/topic.py +59 -0
  21. pub_analyzer/models/work.py +30 -7
  22. pub_analyzer/widgets/author/cards.py +15 -14
  23. pub_analyzer/widgets/author/core.py +80 -115
  24. pub_analyzer/widgets/author/tables.py +1 -1
  25. pub_analyzer/widgets/common/__init__.py +6 -6
  26. pub_analyzer/widgets/common/filesystem.py +16 -13
  27. pub_analyzer/widgets/common/filters.py +111 -0
  28. pub_analyzer/widgets/common/input.py +14 -5
  29. pub_analyzer/widgets/common/selector.py +1 -1
  30. pub_analyzer/widgets/common/summary.py +7 -0
  31. pub_analyzer/widgets/institution/cards.py +13 -15
  32. pub_analyzer/widgets/institution/core.py +81 -115
  33. pub_analyzer/widgets/institution/tables.py +1 -1
  34. pub_analyzer/widgets/report/cards.py +33 -31
  35. pub_analyzer/widgets/report/concept.py +47 -0
  36. pub_analyzer/widgets/report/core.py +90 -20
  37. pub_analyzer/widgets/report/export.py +2 -2
  38. pub_analyzer/widgets/report/grants.py +46 -0
  39. pub_analyzer/widgets/report/locations.py +14 -12
  40. pub_analyzer/widgets/report/source.py +22 -14
  41. pub_analyzer/widgets/report/topic.py +55 -0
  42. pub_analyzer/widgets/report/work.py +70 -34
  43. pub_analyzer/widgets/search/__init__.py +4 -4
  44. pub_analyzer/widgets/search/results.py +15 -16
  45. pub_analyzer/widgets/sidebar.py +11 -9
  46. {pub_analyzer-0.1.2.dist-info → pub_analyzer-0.3.0.dist-info}/METADATA +31 -7
  47. pub_analyzer-0.3.0.dist-info/RECORD +69 -0
  48. {pub_analyzer-0.1.2.dist-info → pub_analyzer-0.3.0.dist-info}/WHEEL +1 -1
  49. pub_analyzer/css/author.tcss +0 -78
  50. pub_analyzer/css/institution.tcss +0 -78
  51. pub_analyzer-0.1.2.dist-info/RECORD +0 -62
  52. {pub_analyzer-0.1.2.dist-info → pub_analyzer-0.3.0.dist-info}/LICENSE +0 -0
  53. {pub_analyzer-0.1.2.dist-info → pub_analyzer-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -11,8 +11,8 @@ from pub_analyzer.models.report import AuthorReport, InstitutionReport, WorkRepo
11
11
  from pub_analyzer.models.work import Work
12
12
  from pub_analyzer.widgets.common import Card
13
13
 
14
- # Works pane cards.
15
14
 
15
+ # Works pane cards.
16
16
  class ReportCitationMetricsCard(Card):
17
17
  """Citation metrics for this report."""
18
18
 
@@ -22,20 +22,20 @@ class ReportCitationMetricsCard(Card):
22
22
 
23
23
  def compose(self) -> ComposeResult:
24
24
  """Compose card."""
25
- yield Label('[italic]Citation metrics:[/italic]', classes="card-title")
25
+ yield Label("[italic]Citation metrics:[/italic]", classes="card-title")
26
26
 
27
- with Vertical(classes='card-container'):
28
- type_a_count = self.report.citation_resume.type_a_count
29
- type_b_count = self.report.citation_resume.type_b_count
27
+ with Vertical(classes="card-container"):
28
+ type_a_count = self.report.citation_summary.type_a_count
29
+ type_b_count = self.report.citation_summary.type_b_count
30
30
  cited_by_count = type_a_count + type_b_count
31
31
 
32
- yield Label(f'[bold]Count:[/bold] {cited_by_count}')
33
- yield Label(f'[bold]Type A:[/bold] {type_a_count}')
34
- yield Label(f'[bold]Type B:[/bold] {type_b_count}')
32
+ yield Label(f"[bold]Count:[/bold] {cited_by_count}")
33
+ yield Label(f"[bold]Type A:[/bold] {type_a_count}")
34
+ yield Label(f"[bold]Type B:[/bold] {type_b_count}")
35
35
 
36
36
 
37
- class WorksTypeResumeCard(Card):
38
- """Works Type Counters Resume Card."""
37
+ class WorksTypeSummaryCard(Card):
38
+ """Works Type Counters Summary Card."""
39
39
 
40
40
  def __init__(self, report: AuthorReport | InstitutionReport) -> None:
41
41
  self.report = report
@@ -43,14 +43,14 @@ class WorksTypeResumeCard(Card):
43
43
 
44
44
  def compose(self) -> ComposeResult:
45
45
  """Compose card."""
46
- yield Label('[italic]Work Type[/italic]', classes='card-title')
46
+ yield Label("[italic]Work Type[/italic]", classes="card-title")
47
47
 
48
- with VerticalScroll(classes='card-container'):
49
- for work_type_counter in self.report.works_type_resume:
50
- yield Label(f'[bold]{work_type_counter.type_name}:[/bold] {work_type_counter.count}')
48
+ with VerticalScroll(classes="card-container"):
49
+ for work_type_counter in self.report.works_type_summary:
50
+ yield Label(f"[bold]{work_type_counter.type_name}:[/bold] {work_type_counter.count}")
51
51
 
52
52
 
53
- class OpenAccessResumeCard(Card):
53
+ class OpenAccessSummaryCard(Card):
54
54
  """Open Access counts for this report."""
55
55
 
56
56
  def __init__(self, report: AuthorReport | InstitutionReport) -> None:
@@ -59,11 +59,11 @@ class OpenAccessResumeCard(Card):
59
59
 
60
60
  def compose(self) -> ComposeResult:
61
61
  """Compose card."""
62
- yield Label('[italic]Open Access[/italic]', classes='card-title')
62
+ yield Label("[italic]Open Access[/italic]", classes="card-title")
63
63
 
64
- with VerticalScroll(classes='card-container'):
65
- for status, count in self.report.open_access_resume.model_dump().items():
66
- yield Label(f'[bold]{status}:[/bold] {count}')
64
+ with VerticalScroll(classes="card-container"):
65
+ for status, count in self.report.open_access_summary.model_dump().items():
66
+ yield Label(f"[bold]{status}:[/bold] {count}")
67
67
 
68
68
 
69
69
  # Work Info cards.
@@ -77,18 +77,20 @@ class AuthorshipCard(Card):
77
77
 
78
78
  def compose(self) -> ComposeResult:
79
79
  """Compose card."""
80
- yield Label('[italic]Authorships[/italic]', classes='card-title')
80
+ yield Label("[italic]Authorships[/italic]", classes="card-title")
81
81
 
82
- with VerticalScroll(classes='card-container'):
82
+ with VerticalScroll(classes="card-container"):
83
83
  for authorship in self.work.authorships:
84
84
  # If the author was provided, highlight
85
85
  if self.author and authorship.author.display_name == self.author.display_name:
86
- author_name_formated = f'[b #909d63]{authorship.author.display_name}[/]'
86
+ author_name_formated = f"[b #909d63]{authorship.author.display_name}[/]"
87
87
  else:
88
88
  author_name_formated = str(authorship.author.display_name)
89
89
 
90
90
  external_id = authorship.author.orcid or authorship.author.id
91
- yield Label(f"""- [b]{authorship.author_position}:[/b] [@click=app.open_link('{quote(str(external_id))}')]{author_name_formated}[/]""") # noqa: E501
91
+ yield Label(
92
+ f"""- [b]{authorship.author_position}:[/b] [@click=app.open_link('{quote(str(external_id))}')]{author_name_formated}[/]""" # noqa: E501
93
+ )
92
94
 
93
95
 
94
96
  class OpenAccessCard(Card):
@@ -102,8 +104,8 @@ class OpenAccessCard(Card):
102
104
  """Compose card."""
103
105
  work_url = self.work.open_access.oa_url
104
106
 
105
- yield Label('[italic]Open Access[/italic]', classes='card-title')
106
- yield Label(f'[bold]Status:[/bold] {self.work.open_access.oa_status.value}')
107
+ yield Label("[italic]Open Access[/italic]", classes="card-title")
108
+ yield Label(f"[bold]Status:[/bold] {self.work.open_access.oa_status.value}")
107
109
  if work_url:
108
110
  yield Label(f"""[bold]URL:[/bold] [@click=app.open_link('{quote(str(work_url))}')]{work_url}[/]""")
109
111
 
@@ -117,12 +119,12 @@ class CitationMetricsCard(Card):
117
119
 
118
120
  def compose(self) -> ComposeResult:
119
121
  """Compose card."""
120
- type_a_count = self.work_report.citation_resume.type_a_count
121
- type_b_count = self.work_report.citation_resume.type_b_count
122
+ type_a_count = self.work_report.citation_summary.type_a_count
123
+ type_b_count = self.work_report.citation_summary.type_b_count
122
124
  cited_by_count = type_a_count + type_b_count
123
125
 
124
- yield Label('[italic]Citation[/italic]', classes='card-title')
126
+ yield Label("[italic]Citation[/italic]", classes="card-title")
125
127
 
126
- yield Label(f'[bold]Count:[/bold] {cited_by_count}')
127
- yield Label(f'[bold]Type A:[/bold] {type_a_count}')
128
- yield Label(f'[bold]Type B:[/bold] {type_b_count}')
128
+ yield Label(f"[bold]Count:[/bold] {cited_by_count}")
129
+ yield Label(f"[bold]Type A:[/bold] {type_a_count}")
130
+ yield Label(f"[bold]Type B:[/bold] {type_b_count}")
@@ -0,0 +1,47 @@
1
+ """Concepts Widgets."""
2
+
3
+ from urllib.parse import quote
4
+
5
+ from rich.table import Table
6
+ from rich.text import Text
7
+ from textual.app import ComposeResult
8
+ from textual.widgets import Static
9
+
10
+ from pub_analyzer.models.concept import DehydratedConcept
11
+
12
+
13
+ class ConceptsTable(Static):
14
+ """All Concepts from a work in a table."""
15
+
16
+ DEFAULT_CSS = """
17
+ ConceptsTable .concepts-table {
18
+ height: auto;
19
+ padding: 1 2 0 2;
20
+ }
21
+ """
22
+
23
+ def __init__(self, concepts_list: list[DehydratedConcept]) -> None:
24
+ self.concepts_list = concepts_list
25
+ super().__init__()
26
+
27
+ def compose(self) -> ComposeResult:
28
+ """Compose Table."""
29
+ concepts_table = Table(title="Concepts", expand=True, show_lines=True)
30
+
31
+ # Define Columns
32
+ concepts_table.add_column("", justify="center", vertical="middle")
33
+ concepts_table.add_column("Name", ratio=5)
34
+ concepts_table.add_column("Score", ratio=1)
35
+ concepts_table.add_column("Level", ratio=1)
36
+
37
+ for idx, concept in enumerate(self.concepts_list):
38
+ name = f"""[@click=app.open_link('{quote(str(concept.wikidata))}')][u]{concept.display_name}[/u][/]"""
39
+
40
+ concepts_table.add_row(
41
+ str(idx),
42
+ Text.from_markup(name, overflow="ellipsis"),
43
+ Text.from_markup(f"{concept.score:.2f}"),
44
+ Text.from_markup(f"{concept.level:.1f}"),
45
+ )
46
+
47
+ yield Static(concepts_table, classes="concepts-table")
@@ -3,16 +3,19 @@
3
3
  import datetime
4
4
  import pathlib
5
5
  from enum import Enum
6
+ from typing import ClassVar
6
7
 
7
8
  import httpx
8
9
  from pydantic import TypeAdapter, ValidationError
9
10
  from textual import on
10
11
  from textual.app import ComposeResult
12
+ from textual.binding import Binding, BindingType
11
13
  from textual.containers import Container, Horizontal
14
+ from textual.reactive import reactive
12
15
  from textual.widget import Widget
13
16
  from textual.widgets import Button, LoadingIndicator, Static, TabbedContent, TabPane
14
17
 
15
- from pub_analyzer.internal.report import make_author_report, make_institution_report
18
+ from pub_analyzer.internal.report import FromDate, ToDate, make_author_report, make_institution_report
16
19
  from pub_analyzer.models.author import Author
17
20
  from pub_analyzer.models.institution import Institution
18
21
  from pub_analyzer.models.report import AuthorReport, InstitutionReport
@@ -28,6 +31,17 @@ from .work import WorkReportPane
28
31
  class ReportWidget(Static):
29
32
  """Base report widget."""
30
33
 
34
+ BINDINGS: ClassVar[list[BindingType]] = [
35
+ Binding(key="ctrl+y", action="toggle_works", description="Toggle empty works"),
36
+ ]
37
+
38
+ show_empty_works: reactive[bool] = reactive(True)
39
+
40
+ async def action_toggle_works(self) -> None:
41
+ """Toggle show empty works attribute."""
42
+ self.show_empty_works = not self.show_empty_works
43
+ await self.query_one(WorkReportPane).toggle_empty_works()
44
+
31
45
 
32
46
  class AuthorReportWidget(ReportWidget):
33
47
  """Author report generator view."""
@@ -96,11 +110,11 @@ class CreateReportWidget(Static):
96
110
  self.query_one(LoadingIndicator).display = False
97
111
  status_error = f"HTTP Exception for url: {exc.request.url}. Status code: {exc.response.status_code}"
98
112
  self.app.notify(
99
- title="Error making report!",
100
- message=f"The report could not be generated due to a problem with the OpenAlex API. {status_error}",
101
- severity="error",
102
- timeout=20.0
103
- )
113
+ title="Error making report!",
114
+ message=f"The report could not be generated due to a problem with the OpenAlex API. {status_error}",
115
+ severity="error",
116
+ timeout=20.0,
117
+ )
104
118
  return None
105
119
 
106
120
  container = self.query_one(Container)
@@ -114,32 +128,82 @@ class CreateReportWidget(Static):
114
128
  class CreateAuthorReportWidget(CreateReportWidget):
115
129
  """Widget Author report wrapper to load data from API."""
116
130
 
117
- def __init__(self, author: Author, from_date: datetime.date | None = None, to_date: datetime.date | None = None) -> None:
131
+ def __init__(
132
+ self,
133
+ author: Author,
134
+ pub_from_date: datetime.datetime | None = None,
135
+ pub_to_date: datetime.datetime | None = None,
136
+ cited_from_date: datetime.datetime | None = None,
137
+ cited_to_date: datetime.datetime | None = None,
138
+ ) -> None:
118
139
  self.author = author
119
- self.from_date = from_date
120
- self.to_date = to_date
140
+
141
+ # Author publication date range
142
+ self.pub_from_date = pub_from_date
143
+ self.pub_to_date = pub_to_date
144
+
145
+ # Cited date range
146
+ self.cited_from_date = cited_from_date
147
+ self.cited_to_date = cited_to_date
121
148
 
122
149
  super().__init__()
123
150
 
124
151
  async def make_report(self) -> AuthorReportWidget:
125
152
  """Make report and create the widget."""
126
- report = await make_author_report(author=self.author, from_date=self.from_date, to_date=self.to_date)
153
+ pub_from_date = FromDate(self.pub_from_date) if self.pub_from_date else None
154
+ pub_to_date = ToDate(self.pub_to_date) if self.pub_to_date else None
155
+
156
+ cited_from_date = FromDate(self.cited_from_date) if self.cited_from_date else None
157
+ cited_to_date = ToDate(self.cited_to_date) if self.cited_to_date else None
158
+
159
+ report = await make_author_report(
160
+ author=self.author,
161
+ pub_from_date=pub_from_date,
162
+ pub_to_date=pub_to_date,
163
+ cited_from_date=cited_from_date,
164
+ cited_to_date=cited_to_date,
165
+ )
127
166
  return AuthorReportWidget(report=report)
128
167
 
129
168
 
130
169
  class CreateInstitutionReportWidget(CreateReportWidget):
131
170
  """Widget Institution report wrapper to load data from API."""
132
171
 
133
- def __init__(self, institution: Institution, from_date: datetime.date | None = None, to_date: datetime.date | None = None) -> None:
172
+ def __init__(
173
+ self,
174
+ institution: Institution,
175
+ pub_from_date: datetime.datetime | None = None,
176
+ pub_to_date: datetime.datetime | None = None,
177
+ cited_from_date: datetime.datetime | None = None,
178
+ cited_to_date: datetime.datetime | None = None,
179
+ ) -> None:
134
180
  self.institution = institution
135
- self.from_date = from_date
136
- self.to_date = to_date
181
+
182
+ # Institution publication date range
183
+ self.pub_from_date = pub_from_date
184
+ self.pub_to_date = pub_to_date
185
+
186
+ # Cited date range
187
+ self.cited_from_date = cited_from_date
188
+ self.cited_to_date = cited_to_date
137
189
 
138
190
  super().__init__()
139
191
 
140
192
  async def make_report(self) -> InstitutionReportWidget:
141
193
  """Make report and create the widget."""
142
- report = await make_institution_report(institution=self.institution, from_date=self.from_date, to_date=self.to_date)
194
+ pub_from_date = FromDate(self.pub_from_date) if self.pub_from_date else None
195
+ pub_to_date = ToDate(self.pub_to_date) if self.pub_to_date else None
196
+
197
+ cited_from_date = FromDate(self.cited_from_date) if self.cited_from_date else None
198
+ cited_to_date = ToDate(self.cited_to_date) if self.cited_to_date else None
199
+
200
+ report = await make_institution_report(
201
+ institution=self.institution,
202
+ pub_from_date=pub_from_date,
203
+ pub_to_date=pub_to_date,
204
+ cited_from_date=cited_from_date,
205
+ cited_to_date=cited_to_date,
206
+ )
143
207
  return InstitutionReportWidget(report=report)
144
208
 
145
209
 
@@ -199,11 +263,11 @@ class LoadReportWidget(Static):
199
263
  main_content.update_title(title=institution_report.institution.display_name)
200
264
  except ValidationError:
201
265
  self.app.notify(
202
- title="Error loading report!",
203
- message="The report does not have the correct structure. This may be because it is an old version or because it is not of the specified type.", # noqa: E501
204
- severity="error",
205
- timeout=10.0
206
- )
266
+ title="Error loading report!",
267
+ message="The report does not have the correct structure. This may be because it is an old version or because it is not of the specified type.", # noqa: E501
268
+ severity="error",
269
+ timeout=10.0,
270
+ )
207
271
 
208
272
  @on(Select.Changed)
209
273
  async def on_select_entity(self, event: Select.Changed) -> None:
@@ -221,7 +285,13 @@ class LoadReportWidget(Static):
221
285
  with Horizontal(classes="filesystem-selector-container"):
222
286
  entity_options = [(name.title(), endpoint) for name, endpoint in self.EntityType.__members__.items()]
223
287
 
224
- yield FileSystemSelector(path=pathlib.Path.home(), only_dir=False, extension=[".json",])
288
+ yield FileSystemSelector(
289
+ path=pathlib.Path.home(),
290
+ only_dir=False,
291
+ extension=[
292
+ ".json",
293
+ ],
294
+ )
225
295
  yield self.EntityTypeSelector(options=entity_options, value=self.entity_handler, allow_blank=False)
226
296
 
227
297
  with Horizontal(classes="button-container"):
@@ -75,7 +75,7 @@ class ExportReportPane(VerticalScroll):
75
75
  self.app.notify,
76
76
  title="Report exported successfully!",
77
77
  message=f"The report was exported correctly. You can go see it at [i]{file_path}[/]",
78
- timeout=20.0
78
+ timeout=20.0,
79
79
  )
80
80
 
81
81
  @on(Button.Pressed, "#export-report-button")
@@ -98,7 +98,7 @@ class ExportReportPane(VerticalScroll):
98
98
  with Vertical(classes="export-form-input-container"):
99
99
  yield Label("[b]Name File:[/]", classes="export-form-label")
100
100
  with Horizontal(classes="file-selector-container"):
101
- type_options = [(name, value) for name, value in self.ExportFileType.__members__.items()]
101
+ type_options = list(self.ExportFileType.__members__.items())
102
102
  selector_disabled = isinstance(self.report, InstitutionReport)
103
103
 
104
104
  yield Input(value=suggest_file_name, placeholder="report.json", classes="export-form-input")
@@ -0,0 +1,46 @@
1
+ """Grants Widgets."""
2
+
3
+ from urllib.parse import quote
4
+
5
+ from rich.table import Table
6
+ from rich.text import Text
7
+ from textual.app import ComposeResult
8
+ from textual.widgets import Static
9
+
10
+ from pub_analyzer.models.work import Grant
11
+
12
+
13
+ class GrantsTable(Static):
14
+ """All Grants from a work in a table."""
15
+
16
+ DEFAULT_CSS = """
17
+ GrantsTable .grants-table {
18
+ height: auto;
19
+ padding: 1 2 0 2;
20
+ }
21
+ """
22
+
23
+ def __init__(self, grants_list: list[Grant]) -> None:
24
+ self.grants_list = grants_list
25
+ super().__init__()
26
+
27
+ def compose(self) -> ComposeResult:
28
+ """Compose Table."""
29
+ grants_table = Table(title="Grants", expand=True, show_lines=True)
30
+
31
+ # Define Columns
32
+ grants_table.add_column("", justify="center", vertical="middle")
33
+ grants_table.add_column("Name", ratio=3)
34
+ grants_table.add_column("Award ID", ratio=2)
35
+
36
+ for idx, grant in enumerate(self.grants_list):
37
+ name = f"""[@click=app.open_link('{quote(str(grant.funder))}')][u]{grant.funder_display_name}[/u][/]"""
38
+ award_id = grant.award_id or "-"
39
+
40
+ grants_table.add_row(
41
+ str(idx),
42
+ Text.from_markup(name, overflow="ellipsis"),
43
+ Text.from_markup(award_id),
44
+ )
45
+
46
+ yield Static(grants_table, classes="grants-table")
@@ -26,17 +26,17 @@ class LocationsTable(Static):
26
26
 
27
27
  def compose(self) -> ComposeResult:
28
28
  """Compose Table."""
29
- locations_table = Table(title='Locations', expand=True, show_lines=True)
29
+ locations_table = Table(title="Locations", expand=True, show_lines=True)
30
30
 
31
31
  # Define Columns
32
- locations_table.add_column('', justify='center', vertical='middle')
33
- locations_table.add_column('Name', ratio=3)
34
- locations_table.add_column('Publisher or institution', ratio=2)
35
- locations_table.add_column('Type')
36
- locations_table.add_column('ISSN-L')
37
- locations_table.add_column('Is OA')
38
- locations_table.add_column('License')
39
- locations_table.add_column('version')
32
+ locations_table.add_column("", justify="center", vertical="middle")
33
+ locations_table.add_column("Name", ratio=3)
34
+ locations_table.add_column("Publisher or institution", ratio=2)
35
+ locations_table.add_column("Type")
36
+ locations_table.add_column("ISSN-L")
37
+ locations_table.add_column("Is OA")
38
+ locations_table.add_column("License")
39
+ locations_table.add_column("version")
40
40
 
41
41
  for idx, location in enumerate(self.locations_list):
42
42
  if location.source:
@@ -46,7 +46,9 @@ class LocationsTable(Static):
46
46
  issn_l = source.issn_l if source.issn_l else "-"
47
47
 
48
48
  if source.host_organization_name and source.host_organization:
49
- publisher = f"""[@click=app.open_link('{quote(str(source.host_organization))}')][u]{source.host_organization_name}[/u][/]""" # noqa: E501
49
+ publisher = (
50
+ f"""[@click=app.open_link('{quote(str(source.host_organization))}')][u]{source.host_organization_name}[/u][/]"""
51
+ )
50
52
  else:
51
53
  publisher = source.host_organization_name if source.host_organization_name else "-"
52
54
  else:
@@ -61,7 +63,7 @@ class LocationsTable(Static):
61
63
 
62
64
  locations_table.add_row(
63
65
  str(idx),
64
- Text.from_markup(title, overflow='ellipsis'),
66
+ Text.from_markup(title, overflow="ellipsis"),
65
67
  Text.from_markup(publisher),
66
68
  Text.from_markup(type),
67
69
  Text.from_markup(issn_l),
@@ -70,4 +72,4 @@ class LocationsTable(Static):
70
72
  Text.from_markup(version.capitalize()),
71
73
  )
72
74
 
73
- yield Static(locations_table, classes='locations-table')
75
+ yield Static(locations_table, classes="locations-table")
@@ -9,7 +9,7 @@ from textual.containers import VerticalScroll
9
9
  from textual.widgets import Static
10
10
 
11
11
  from pub_analyzer.models.report import AuthorReport, InstitutionReport
12
- from pub_analyzer.models.source import DehydratedSource
12
+ from pub_analyzer.models.source import Source
13
13
 
14
14
 
15
15
  class SourcesTable(Static):
@@ -22,44 +22,52 @@ class SourcesTable(Static):
22
22
  }
23
23
  """
24
24
 
25
- def __init__(self, sources_list: list[DehydratedSource]) -> None:
25
+ def __init__(self, sources_list: list[Source]) -> None:
26
26
  self.sources_list = sources_list
27
27
  super().__init__()
28
28
 
29
29
  def compose(self) -> ComposeResult:
30
30
  """Compose Table."""
31
- sources_table = Table(title='Sources', expand=True, show_lines=True)
31
+ sources_table = Table(title="Sources", expand=True, show_lines=True)
32
32
 
33
33
  # Define Columns
34
- sources_table.add_column('', justify='center', vertical='middle')
35
- sources_table.add_column('Name', ratio=3)
36
- sources_table.add_column('Publisher or institution', ratio=2)
37
- sources_table.add_column('Type')
38
- sources_table.add_column('ISSN-L')
39
- sources_table.add_column('Is Open Access')
34
+ sources_table.add_column("", justify="center", vertical="middle")
35
+ sources_table.add_column("Name", ratio=3)
36
+ sources_table.add_column("Publisher or institution", ratio=2)
37
+ sources_table.add_column("Type")
38
+ sources_table.add_column("ISSN-L")
39
+ sources_table.add_column("Impact factor")
40
+ sources_table.add_column("h-index")
41
+ sources_table.add_column("Is OA")
40
42
 
41
43
  for idx, source in enumerate(self.sources_list):
42
44
  if source.host_organization_name:
43
- host_organization = f"""[@click=app.open_link('{quote(str(source.host_organization))}')][u]{source.host_organization_name}[/u][/]""" # noqa: E501
45
+ host_organization = (
46
+ f"""[@click=app.open_link('{quote(str(source.host_organization))}')][u]{source.host_organization_name}[/u][/]"""
47
+ )
44
48
  else:
45
49
  host_organization = "-"
46
50
 
47
51
  title = f"""[@click=app.open_link('{quote(str(source.id))}')][u]{source.display_name}[/u][/]"""
48
52
  type_source = source.type
49
53
  issn_l = source.issn_l if source.issn_l else "-"
54
+ impact_factor = f"{source.summary_stats.two_yr_mean_citedness:.3f}"
55
+ h_index = f"{source.summary_stats.h_index}"
56
+
50
57
  is_open_access = "[#909d63]True[/]" if source.is_oa else "[#bc5653]False[/]"
51
58
 
52
59
  sources_table.add_row(
53
60
  str(idx),
54
- Text.from_markup(title, overflow='ellipsis'),
61
+ Text.from_markup(title, overflow="ellipsis"),
55
62
  Text.from_markup(host_organization),
56
63
  Text.from_markup(type_source),
57
64
  Text.from_markup(issn_l),
65
+ Text.from_markup(impact_factor),
66
+ Text.from_markup(h_index),
58
67
  Text.from_markup(is_open_access),
59
68
  )
60
69
 
61
- yield Static(sources_table, classes='sources-table')
62
-
70
+ yield Static(sources_table, classes="sources-table")
63
71
 
64
72
 
65
73
  class SourcesReportPane(VerticalScroll):
@@ -79,4 +87,4 @@ class SourcesReportPane(VerticalScroll):
79
87
 
80
88
  def compose(self) -> ComposeResult:
81
89
  """Compose content pane."""
82
- yield SourcesTable(sources_list=self.report.sources_resume.sources)
90
+ yield SourcesTable(sources_list=self.report.sources_summary.sources)
@@ -0,0 +1,55 @@
1
+ """Topics Widgets."""
2
+
3
+ from urllib.parse import quote
4
+
5
+ from rich.table import Table
6
+ from rich.text import Text
7
+ from textual.app import ComposeResult
8
+ from textual.widgets import Static
9
+
10
+ from pub_analyzer.models.topic import DehydratedTopic
11
+
12
+
13
+ class TopicsTable(Static):
14
+ """All Topics from a work in a table."""
15
+
16
+ DEFAULT_CSS = """
17
+ TopicsTable .topics-table {
18
+ height: auto;
19
+ padding: 1 2 0 2;
20
+ }
21
+ """
22
+
23
+ def __init__(self, topics_list: list[DehydratedTopic]) -> None:
24
+ self.topics_list = topics_list
25
+ super().__init__()
26
+
27
+ def compose(self) -> ComposeResult:
28
+ """Compose Table."""
29
+ topics_table = Table(title="Topics", expand=True, show_lines=True)
30
+
31
+ # Define Columns
32
+ topics_table.add_column("", justify="center", vertical="middle")
33
+ topics_table.add_column("Name", ratio=3)
34
+ topics_table.add_column("Score", ratio=1)
35
+ topics_table.add_column("Domain", ratio=1)
36
+ topics_table.add_column("Field", ratio=1)
37
+ topics_table.add_column("SubField", ratio=1)
38
+
39
+ for idx, topic in enumerate(self.topics_list):
40
+ name = f"""[@click=app.open_link('{quote(str(topic.id))}')][u]{topic.display_name}[/u][/]"""
41
+
42
+ domain = f"""[@click=app.open_link('{quote(str(topic.domain.id))}')][u]{topic.domain.display_name}[/u][/]"""
43
+ field = f"""[@click=app.open_link('{quote(str(topic.field.id))}')][u]{topic.field.display_name}[/u][/]"""
44
+ subfield = f"""[@click=app.open_link('{quote(str(topic.subfield.id))}')][u]{topic.subfield.display_name}[/u][/]"""
45
+
46
+ topics_table.add_row(
47
+ str(idx),
48
+ Text.from_markup(name, overflow="ellipsis"),
49
+ Text.from_markup(f"{topic.score:.2f}"),
50
+ Text.from_markup(domain),
51
+ Text.from_markup(field),
52
+ Text.from_markup(subfield),
53
+ )
54
+
55
+ yield Static(topics_table, classes="topics-table")