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

@@ -15,6 +15,10 @@ $primary-color-highlight: #dc2626;
15
15
  ReportWidget {
16
16
  height: 1fr;
17
17
  margin: 1 2;
18
+
19
+ #main-container {
20
+ height: 100%;
21
+ }
18
22
  }
19
23
 
20
24
  .-dark-mode ReportWidget {
@@ -22,14 +26,6 @@ ReportWidget {
22
26
  color: $text-primary-color-darken;
23
27
  }
24
28
 
25
- ReportWidget #main-container {
26
- height: 100%;
27
- }
28
-
29
- ContentSwitcher {
30
- height: 1fr;
31
- }
32
-
33
29
  /* Common */
34
30
  ReportWidget .cards-container {
35
31
  height: auto;
@@ -37,7 +33,7 @@ ReportWidget .cards-container {
37
33
 
38
34
  layout: grid;
39
35
  grid-size: 3 1;
40
- grid-rows: 12;
36
+ grid-rows: 13;
41
37
  grid-columns: 1fr;
42
38
  grid-gutter: 1 2;
43
39
  }
@@ -70,7 +70,7 @@ def _get_institution_keys(
70
70
 
71
71
 
72
72
  def _get_authors_list(authorships: list[Authorship]) -> list[str]:
73
- """Collect OpenAlex IDs from authors in a list of authorships.
73
+ """Collect OpenAlex IDs from authors in a list of authorship's.
74
74
 
75
75
  Args:
76
76
  authorships: List of authorships.
@@ -108,7 +108,7 @@ def _get_citation_type(original_work_authors: list[str], cited_work_authors: lis
108
108
 
109
109
 
110
110
  def _add_work_abstract(work: dict[str, Any]) -> dict[str, Any]:
111
- """Get work abtract from abstract_inverted_index and insert new key `abstract`.
111
+ """Get work abstract from abstract_inverted_index and insert new key `abstract`.
112
112
 
113
113
  Args:
114
114
  work: Raw work.
@@ -95,7 +95,7 @@
95
95
  plot.plot(
96
96
  size: (0.90, 0.48),
97
97
  axis-style: "scientific-auto",
98
- plot-style: (stroke: (1pt + BLUE),),
98
+ plot-style: (stroke: (1pt + PALETTE.at(0)),),
99
99
  x-min: auto, x-max: auto,
100
100
  x-tick-step: 1, y-tick-step: auto,
101
101
  x-label: none, y-label: none,
@@ -1,25 +1,43 @@
1
- // This document was generated using Pub Analyzer.
2
- // https://pub-analyzer.com
1
+ // This document was generated using Pub Analyzer version {{ version }}.
2
+ //
3
+ // Pub Analyzer is a tool designed to retrieve, process and present in a concise and understandable
4
+ // way the scientific production of a researcher, including detailed information about their articles,
5
+ // citations, collaborations and other relevant metrics.
6
+ //
7
+ // See more here: https://pub-analyzer.com
3
8
 
4
9
  // Packages
10
+ //
11
+ // This document uses the Cetz package to render plots and graphs. For more information
12
+ // on how to edit the plots see: https://typst.app/universe/package/cetz/
13
+
5
14
  #import "@preview/cetz:0.2.2": canvas, plot, chart, palette
6
15
 
7
16
  // Colors
8
- #let BLUE = rgb("#42a2f8")
9
- #let GREEN = rgb("#82d452")
10
- #let GRAY = rgb("#929292")
11
- #let YELLOW = rgb("#f0bb40")
12
- #let RED = rgb("#eb4025")
13
- #let PURPLE = rgb("#c33375")
14
-
15
- #let colors = (BLUE, GREEN, GRAY, YELLOW, RED, PURPLE)
16
-
17
- // Page Layoput
17
+ //
18
+ // The following variables control all colors used in the document.
19
+ // You can modify the color codes by specifying the four RGB(A) components or by
20
+ // using the hexadecimal code.
21
+ //
22
+ // See more here: https://typst.app/docs/reference/visualize/color/#definitions-rgb
23
+
24
+ #let SUCCESS = rgb("#909d63")
25
+ #let ERROR = rgb("#bc5653")
26
+
27
+ #let CATEGORY_1 = rgb("#42a2f8")
28
+ #let CATEGORY_2 = rgb("#82d452")
29
+ #let CATEGORY_3 = rgb("#929292")
30
+ #let CATEGORY_4 = rgb("#f0bb40")
31
+ #let CATEGORY_5 = rgb("#eb4025")
32
+ #let CATEGORY_6 = rgb("#c33375")
33
+
34
+ #let PALETTE = (CATEGORY_1, CATEGORY_2, CATEGORY_3, CATEGORY_4, CATEGORY_5, CATEGORY_6)
35
+
36
+ // Page Layout
18
37
  #set page("us-letter")
19
38
  #set page(flipped: true)
20
39
 
21
40
  #set heading(numbering: "1.")
22
-
23
41
  #set page(footer: grid(
24
42
  columns: (1fr, 1fr),
25
43
  align(left)[Made with #link("https://pub-analyzer.com")[_pub-analyzer_] version {{ version }}],
@@ -50,6 +68,7 @@
50
68
  #grid(
51
69
  columns: (1fr),
52
70
  row-gutter: 11pt,
71
+
53
72
  [#align(center, text(size: 17pt, weight: "bold")[{{ report.author.display_name }}])],
54
73
  {% if report.author.last_known_institutions %}
55
74
  {% set last_known_institution = report.author.last_known_institutions[0] %}
@@ -17,6 +17,6 @@
17
17
  [{{ source.issn_l or "-" }}],
18
18
  [{{ source.summary_stats.two_yr_mean_citedness|round(3) }}],
19
19
  [{{ source.summary_stats.h_index }}],
20
- [{% if source.is_oa %}#text(rgb("909d63"))[True]{% else %}#text(rgb("bc5653"))[False]{% endif %}],
20
+ [{% if source.is_oa %}#text(rgb(SUCCESS))[True]{% else %}#text(rgb(ERROR))[False]{% endif %}],
21
21
  {% endfor %}
22
22
  )
@@ -17,7 +17,7 @@
17
17
  ]
18
18
  ],
19
19
  [
20
- #block(width: 100%, height: 100%, inset: (x: 5pt, y: 10pt))[#body]
20
+ #block(width: 100%, height: 100%, inset: (x: 0pt, y: 10pt))[#body]
21
21
  ],
22
22
  )
23
23
  }
@@ -34,7 +34,7 @@
34
34
  {{ report.citation_summary.type_b_count }} // Type B
35
35
  ),
36
36
  radius: 1,
37
- slice-style: (BLUE, GREEN),
37
+ slice-style: (PALETTE.at(0), PALETTE.at(1)),
38
38
  outer-label: (content: "%", radius: 115%),
39
39
  )
40
40
  })
@@ -48,8 +48,8 @@
48
48
  grid.cell(colspan: 2)[
49
49
  *Count:* {{ report.citation_summary.type_a_count + report.citation_summary.type_b_count }}
50
50
  ],
51
- [#box(height: 7pt, width: 7pt, fill: BLUE) *Type A:* {{ report.citation_summary.type_a_count }}],
52
- [#box(height: 7pt, width: 7pt, fill: GREEN) *Type B:* {{ report.citation_summary.type_b_count }}],
51
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(0)) *Type A:* {{ report.citation_summary.type_a_count }}],
52
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(1)) *Type B:* {{ report.citation_summary.type_b_count }}],
53
53
  )
54
54
  ]
55
55
  ],
@@ -61,7 +61,7 @@
61
61
  y-grid: false,
62
62
  bar-style: palette.new(
63
63
  base: (stroke: none, fill: none),
64
- colors: colors
64
+ colors: PALETTE
65
65
  ),
66
66
  (
67
67
  {% for work_type in report.works_type_summary[:4] %}
@@ -83,7 +83,7 @@
83
83
 
84
84
  {% for work_type in report.works_type_summary[:4] %}
85
85
  [
86
- #box(height: 7pt, width: 7pt, fill: colors.at({{ loop.index0 }})) *{{ work_type.type_name|capitalize }}:* {{ work_type.count }}
86
+ #box(height: 7pt, width: 7pt, fill: PALETTE.at({{ loop.index0 }})) *{{ work_type.type_name|capitalize }}:* {{ work_type.count }}
87
87
  ],
88
88
  {% endfor %}
89
89
  )
@@ -94,15 +94,16 @@
94
94
  canvas(length: 35%, {
95
95
  chart.piechart(
96
96
  (
97
- {{report.open_access_summary.gold}}, // Gold
98
- {{report.open_access_summary.green}}, // Green
99
- {{report.open_access_summary.hybrid}}, // Hybrid
100
- {{report.open_access_summary.bronze}}, // Bronze
101
- {{report.open_access_summary.closed}}, // Closed
97
+ {{report.open_access_summary.diamond}}, // diamond
98
+ {{report.open_access_summary.gold}}, // Gold
99
+ {{report.open_access_summary.green}}, // Green
100
+ {{report.open_access_summary.hybrid}}, // Hybrid
101
+ {{report.open_access_summary.bronze}}, // Bronze
102
+ {{report.open_access_summary.closed}}, // Closed
102
103
  ),
103
104
  radius: 1,
104
105
  inner-radius: .4,
105
- slice-style: (YELLOW, GREEN, RED, BLUE, GRAY),
106
+ slice-style: (PALETTE.at(0), PALETTE.at(3), PALETTE.at(1), PALETTE.at(4), PALETTE.at(5), PALETTE.at(2)),
106
107
  outer-label: (content: "%", radius: 115%),
107
108
  )
108
109
  })
@@ -117,12 +118,12 @@
117
118
  *Count:* {{ report.open_access_summary.model_dump().items()|sum(attribute="1") }}
118
119
  ],
119
120
 
120
- [#box(height: 7pt, width: 7pt, fill: YELLOW) *Gold:* {{report.open_access_summary.gold}}],
121
- [#box(height: 7pt, width: 7pt, fill: GREEN) *Green:* {{report.open_access_summary.green}}],
122
- [#box(height: 7pt, width: 7pt, fill: BLUE) *Bronze:* {{report.open_access_summary.bronze}}],
123
-
124
- [#box(height: 7pt, width: 7pt, fill: GRAY) *Closed:* {{report.open_access_summary.closed}}],
125
- [#box(height: 7pt, width: 7pt, fill: RED) *Hybrid:* {{report.open_access_summary.hybrid}}],
121
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(0)) *Diamond:* {{report.open_access_summary.diamond}}],
122
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(3)) *Gold:* {{report.open_access_summary.gold}}],
123
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(1)) *Green:* {{report.open_access_summary.green}}],
124
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(5)) *Bronze:* {{report.open_access_summary.bronze}}],
125
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(2)) *Closed:* {{report.open_access_summary.closed}}],
126
+ [#box(height: 7pt, width: 7pt, fill: PALETTE.at(4)) *Hybrid:* {{report.open_access_summary.hybrid}}],
126
127
  )
127
128
  ]
128
129
  ],
@@ -147,6 +148,7 @@
147
148
  [{{ work.citation_summary.type_a_count }}],
148
149
  [{{ work.citation_summary.type_b_count }}],
149
150
  [{{ work.work.open_access.oa_status.value }}],
151
+
150
152
  {% endfor %}
151
153
  )
152
154
  #pagebreak()
@@ -9,7 +9,7 @@
9
9
  #linebreak()
10
10
 
11
11
  {% if work.work.abstract %}
12
- #text()[#"{{ work.work.abstract.replace('"', '\\"') }}"]
12
+ #text()[#"{{ work.work.abstract.replace('"', '\\"')|truncate(1000) }}"]
13
13
 
14
14
  #linebreak()
15
15
 
@@ -23,7 +23,7 @@
23
23
  #align(center)[_Authorships_]
24
24
  #parbreak()
25
25
  {% for authorship in work.work.authorships[:10] %}
26
- - *{{ authorship.author_position }}:* #underline([#link("{{ authorship.author.orcid or authorship.author.id }}")[#text({% if authorship.author.display_name == report.author.display_name %}rgb("909d63"){% endif %})[{{ authorship.author.display_name }}]]])
26
+ - *{{ authorship.author_position }}:* #underline([#link("{{ authorship.author.orcid or authorship.author.id }}")[#text({% if authorship.author.display_name == report.author.display_name %}rgb(SUCCESS){% endif %})[{{ authorship.author.display_name }}]]])
27
27
  {% endfor %}
28
28
  {% if work.work.authorships|length > 10 %}
29
29
  - *...*
@@ -62,7 +62,7 @@
62
62
  [#"{{ cited_by_work.work.title.replace('"', '\\"') }}"],
63
63
  [{{ cited_by_work.work.type }}],
64
64
  [{% if cited_by_work.work.ids.doi %}#underline([#link("{{ cited_by_work.work.ids.doi }}")[DOI]]){% else %}-{% endif %}],
65
- [{% if cited_by_work.citation_type.value == 0 %}#text(rgb("909d63"))[Type A]{% else %}#text(rgb("bc5653"))[Type B]{% endif %}],
65
+ [{% if cited_by_work.citation_type.value == 0 %}#text(rgb(SUCCESS))[Type A]{% else %}#text(rgb(ERROR))[Type B]{% endif %}],
66
66
  [{{ cited_by_work.work.publication_date }}],
67
67
  [{{ cited_by_work.work.cited_by_count }}],
68
68
  {% endfor %}
@@ -87,7 +87,7 @@
87
87
  [{{ location.source.host_organization_name or "-" }}],
88
88
  [{{ location.source.type }}],
89
89
  [{{ location.source.issn_l or "-" }}],
90
- [{% if location.is_oa %}#text(rgb("909d63"))[True]{% else %}#text(rgb("bc5653"))[False]{% endif %}],
90
+ [{% if location.is_oa %}#text(rgb(SUCCESS))[True]{% else %}#text(rgb(ERROR))[False]{% endif %}],
91
91
  [{{ location.license or "-" }}],
92
92
  [{{ location.version.name or "-" }}],
93
93
  {% else %}
@@ -96,7 +96,7 @@
96
96
  [-],
97
97
  [-],
98
98
  [-],
99
- [{% if location.is_oa %}#text(rgb("909d63"))[True]{% else %}#text(rgb("bc5653"))[False]{% endif %}],
99
+ [{% if location.is_oa %}#text(rgb(SUCCESS))[True]{% else %}#text(rgb(ERROR))[False]{% endif %}],
100
100
  [{{ location.license or "-" }}],
101
101
  [{{ location.version.name or "-" }}],
102
102
  {% endif %}
pub_analyzer/main.py CHANGED
@@ -4,6 +4,7 @@ import urllib.parse
4
4
  import webbrowser
5
5
  from typing import ClassVar
6
6
 
7
+ from textual import log
7
8
  from textual._path import CSSPathType
8
9
  from textual.app import App, ComposeResult
9
10
  from textual.binding import Binding, BindingType
@@ -70,9 +71,11 @@ class PubAnalyzerApp(App[DOMNode]):
70
71
 
71
72
  def action_open_link(self, link: str) -> None:
72
73
  """Open a link in the browser."""
73
- self.app.bell()
74
- if link:
74
+ log.info(f"Opening link: {link}")
75
+ if link and (link != "None"):
75
76
  webbrowser.open(urllib.parse.unquote(link))
77
+ else:
78
+ log.warning("Link cannot be empty!")
76
79
 
77
80
 
78
81
  def run() -> None:
@@ -39,6 +39,13 @@ class AuthorSummaryStats(BaseModel):
39
39
  i10_index: int
40
40
 
41
41
 
42
+ class AuthorAffiliation(BaseModel):
43
+ """List of affiliations this author has claimed in their publications."""
44
+
45
+ institution: DehydratedInstitution
46
+ years: list[int]
47
+
48
+
42
49
  class Author(BaseModel):
43
50
  """Author Model Object from OpenAlex API definition."""
44
51
 
@@ -48,13 +55,13 @@ class Author(BaseModel):
48
55
 
49
56
  display_name: str
50
57
  display_name_alternatives: list[str]
58
+ last_known_institutions: list[DehydratedInstitution]
59
+ affiliations: list[AuthorAffiliation]
51
60
 
52
61
  works_count: int
53
62
  cited_by_count: int
54
63
 
55
- last_known_institutions: list[DehydratedInstitution]
56
64
  counts_by_year: list[AuthorYearCount]
57
-
58
65
  summary_stats: AuthorSummaryStats
59
66
 
60
67
  works_api_url: str
@@ -13,7 +13,7 @@ InstitutionOpenAlexKey: TypeAlias = str
13
13
 
14
14
 
15
15
  class InstitutionIDs(BaseModel):
16
- """IDs from a Institution."""
16
+ """IDs from an Institution."""
17
17
 
18
18
  openalex: InstitutionOpenAlexID
19
19
  grid: str | None = None
@@ -56,10 +56,14 @@ class InstitutionGeo(BaseModel):
56
56
 
57
57
  city: str
58
58
  geonames_city_id: str
59
+
59
60
  region: str | None = None
60
61
  country_code: str
61
62
  country: str
62
63
 
64
+ latitude: float
65
+ longitude: float
66
+
63
67
 
64
68
  class InstitutionRoleType(str, Enum):
65
69
  """Possible institution roles."""
@@ -41,6 +41,7 @@ class CitationSummary(BaseModel):
41
41
  class OpenAccessSummary(BaseModel):
42
42
  """Open Access Type counter."""
43
43
 
44
+ diamond: int = 0
44
45
  gold: int = 0
45
46
  green: int = 0
46
47
  hybrid: int = 0
@@ -50,6 +51,8 @@ class OpenAccessSummary(BaseModel):
50
51
  def add_oa_type(self, open_access_type: OpenAccessStatus) -> None:
51
52
  """Add the type of Open Access in the corresponding counter."""
52
53
  match open_access_type:
54
+ case OpenAccessStatus.diamond:
55
+ self.diamond += 1
53
56
  case OpenAccessStatus.gold:
54
57
  self.gold += 1
55
58
  case OpenAccessStatus.green:
@@ -42,6 +42,7 @@ class Location(BaseModel):
42
42
  class OpenAccessStatus(str, Enum):
43
43
  """The Open Access (OA) status of this work."""
44
44
 
45
+ diamond = "diamond"
45
46
  gold = "gold"
46
47
  green = "green"
47
48
  hybrid = "hybrid"
@@ -143,5 +144,5 @@ class Work(BaseModel):
143
144
 
144
145
  @field_validator("authorships", mode="before")
145
146
  def valid_authorships(cls, authorships: list[dict[str, Any]]) -> list[dict[str, Any]]:
146
- """Skip authorships that do not contain enough data."""
147
+ """Skip authorship's that do not contain enough data."""
147
148
  return [authorship for authorship in authorships if authorship["author"].get("id") is not None]
@@ -38,7 +38,7 @@ class _AuthorSummaryWidget(Static):
38
38
  yield IdentifiersCard(author=self.author)
39
39
  yield CitationMetricsCard(author=self.author)
40
40
 
41
- # Work realeted info
41
+ # Work related info
42
42
  with Vertical(classes="block-container"):
43
43
  yield Label("[bold]Work Info:[/bold]", classes="block-title")
44
44
 
@@ -1,5 +1,7 @@
1
1
  """Author Tables Widgets."""
2
2
 
3
+ from urllib.parse import quote
4
+
3
5
  from rich.table import Table
4
6
  from textual.app import ComposeResult
5
7
  from textual.widgets import Static
@@ -22,3 +24,26 @@ class AuthorWorksByYearTable(Static):
22
24
  table.add_row(str(year), str(works_count), str(cited_by_count))
23
25
 
24
26
  yield Static(table)
27
+
28
+
29
+ class AffiliationsTable(Static):
30
+ """Table with all the institutions to which an author has been affiliated."""
31
+
32
+ def __init__(self, author: Author) -> None:
33
+ self.author = author
34
+ super().__init__()
35
+
36
+ def compose(self) -> ComposeResult:
37
+ """Compose Table."""
38
+ table = Table("Institution", "Country", "Type", "Years", title="Affiliations", expand=True, show_lines=True)
39
+ for affiliation in self.author.affiliations:
40
+ institution = affiliation.institution
41
+ institution_name = (
42
+ f"""[@click=app.open_link("{quote(str(institution.ror))}")]{institution.display_name}[/]"""
43
+ if institution.ror
44
+ else f"{institution.display_name}"
45
+ )
46
+ years = ",".join([str(year) for year in affiliation.years])
47
+ table.add_row(str(institution_name), str(institution.country_code.upper()), str(institution.type.name), str(years))
48
+
49
+ yield Static(table)
@@ -24,7 +24,7 @@ class Filter(Static):
24
24
 
25
25
  @property
26
26
  def validation_state(self) -> bool:
27
- """Return true if all valitadtion passes."""
27
+ """Return true if all validation passes."""
28
28
  raise NotImplementedError
29
29
 
30
30
 
@@ -38,7 +38,7 @@ class _InstitutionSummaryWidget(Static):
38
38
  yield IdentifiersCard(institution=self.institution)
39
39
  yield CitationMetricsCard(institution=self.institution)
40
40
 
41
- # Work realeted info
41
+ # Work related info
42
42
  with Vertical(classes="block-container"):
43
43
  yield Label("[bold]Work Info:[/bold]", classes="block-title")
44
44
 
@@ -1,11 +1,12 @@
1
1
  """Author Report Widgets."""
2
2
 
3
3
  from textual.app import ComposeResult
4
- from textual.containers import Container, Horizontal, VerticalScroll
4
+ from textual.containers import Horizontal, VerticalScroll
5
+ from textual.widgets import TabbedContent, TabPane
5
6
 
6
7
  from pub_analyzer.models.report import AuthorReport
7
8
  from pub_analyzer.widgets.author.cards import CitationMetricsCard, IdentifiersCard, LastInstitutionCard
8
- from pub_analyzer.widgets.author.tables import AuthorWorksByYearTable
9
+ from pub_analyzer.widgets.author.tables import AffiliationsTable, AuthorWorksByYearTable
9
10
 
10
11
 
11
12
  class AuthorReportPane(VerticalScroll):
@@ -16,11 +17,11 @@ class AuthorReportPane(VerticalScroll):
16
17
  layout: vertical;
17
18
  overflow-x: hidden;
18
19
  overflow-y: auto;
19
- }
20
20
 
21
- AuthorReportPane .table-container {
22
- margin: 1 0 0 0 ;
23
- height: auto;
21
+ .author-tables-container {
22
+ margin: 1 0 0 0 ;
23
+ height: auto;
24
+ }
24
25
  }
25
26
  """
26
27
 
@@ -35,5 +36,8 @@ class AuthorReportPane(VerticalScroll):
35
36
  yield IdentifiersCard(author=self.report.author)
36
37
  yield CitationMetricsCard(author=self.report.author)
37
38
 
38
- with Container(classes="table-container"):
39
- yield AuthorWorksByYearTable(author=self.report.author)
39
+ with TabbedContent(id="author-tables-container"):
40
+ with TabPane("Citation Metrics"):
41
+ yield AuthorWorksByYearTable(author=self.report.author)
42
+ with TabPane("Institutions"):
43
+ yield AffiliationsTable(author=self.report.author)
@@ -63,12 +63,12 @@ class OpenAccessSummaryCard(Card):
63
63
 
64
64
  with VerticalScroll(classes="card-container"):
65
65
  for status, count in self.report.open_access_summary.model_dump().items():
66
- yield Label(f"[bold]{status}:[/bold] {count}")
66
+ yield Label(f"[bold]{status.capitalize()}:[/bold] {count}")
67
67
 
68
68
 
69
69
  # Work Info cards.
70
70
  class AuthorshipCard(Card):
71
- """Card that enumerate the authorships of a work."""
71
+ """Card that enumerate the authorship's of a work."""
72
72
 
73
73
  def __init__(self, work: Work, author: Author | None) -> None:
74
74
  self.work = work
@@ -77,7 +77,7 @@ 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]Authorship's[/italic]", classes="card-title")
81
81
 
82
82
  with VerticalScroll(classes="card-container"):
83
83
  for authorship in self.work.authorships:
@@ -105,7 +105,7 @@ class OpenAccessCard(Card):
105
105
  work_url = self.work.open_access.oa_url
106
106
 
107
107
  yield Label("[italic]Open Access[/italic]", classes="card-title")
108
- yield Label(f"[bold]Status:[/bold] {self.work.open_access.oa_status.value}")
108
+ yield Label(f"[bold]Status:[/bold] {self.work.open_access.oa_status.value.capitalize()}")
109
109
  if work_url:
110
110
  yield Label(f"""[bold]URL:[/bold] [@click=app.open_link('{quote(str(work_url))}')]{work_url}[/]""")
111
111
 
@@ -101,7 +101,7 @@ class WorkModal(Modal[None]):
101
101
 
102
102
  # Cards
103
103
  with Horizontal(classes="cards-container"):
104
- # Authorships
104
+ # Authorship's
105
105
  yield AuthorshipCard(work=self.work_report.work, author=self.author)
106
106
 
107
107
  # OpenAccess Info
@@ -111,7 +111,7 @@ class WorkModal(Modal[None]):
111
111
  yield CitationMetricsCard(work_report=self.work_report)
112
112
 
113
113
  with TabbedContent(id="tables-container"):
114
- # Abtract if exists
114
+ # Abstract if exists
115
115
  if self.work_report.work.abstract:
116
116
  with TabPane("Abstract"):
117
117
  yield Label(self.work_report.work.abstract, classes="abstract")
@@ -27,13 +27,19 @@ class AuthorResultWidget(ResultWidget):
27
27
 
28
28
  def compose(self) -> ComposeResult:
29
29
  """Compose Author result widget."""
30
+ orcid_link = self.author_result.external_id
31
+
30
32
  yield Button(label=self.author_result.display_name)
31
33
  with Vertical(classes="vertical-content"):
32
34
  # Main info
33
35
  with Horizontal(classes="main-info-container"):
34
36
  yield Label(f"[bold]Cited by count:[/bold] {self.author_result.cited_by_count}", classes="cited-by-count")
35
37
  yield Label(f"[bold]Works count:[/bold] {self.author_result.works_count}", classes="works-count")
36
- yield Label(f"""[@click=app.open_link('{quote(str(self.author_result.external_id))}')]ORCID[/]""", classes="external-id")
38
+
39
+ if orcid_link:
40
+ yield Label(f"""[@click=app.open_link('{quote(str(orcid_link))}')]ORCID[/]""", classes="external-id")
41
+ else:
42
+ yield Label(f"""[@click=app.open_link('{quote(str(self.author_result.id))}')]OpenAlexID[/]""", classes="external-id")
37
43
 
38
44
  # Author hint
39
45
  yield Label(self.author_result.hint or "", classes="text-hint")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pub-analyzer
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: A text user interface, written in python, which automates the generation of scientific production reports using OpenAlex
5
5
  Home-page: https://github.com/alejandrgaspar/pub-analyzer
6
6
  License: MIT
@@ -21,10 +21,11 @@ Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
24
25
  Classifier: Typing :: Typed
25
- Requires-Dist: httpx (==0.27.0)
26
+ Requires-Dist: httpx (==0.27.2)
26
27
  Requires-Dist: jinja2 (==3.1.4)
27
- Requires-Dist: pydantic (==2.7.4)
28
+ Requires-Dist: pydantic (==2.9.2)
28
29
  Requires-Dist: textual (==0.70.0)
29
30
  Requires-Dist: typst (==0.11.1)
30
31
  Project-URL: Documentation, https://pub-analyzer.com/
@@ -5,7 +5,7 @@ pub_analyzer/css/checkbox.tcss,sha256=FblyIHns-r1K0ikOnSJtoTMz57C6iDEcscdFAsJ7s4
5
5
  pub_analyzer/css/collapsible.tcss,sha256=Rh-L5PcIMhnZ7RhY1udd_BcYC1mfCMew2m6o6ty3juE,605
6
6
  pub_analyzer/css/datatable.tcss,sha256=JgdMUPc4fYmZlXi_FxbuD88pegK6Pi4FgDHIfA_TKxo,994
7
7
  pub_analyzer/css/main.tcss,sha256=rtHLKCVZaBuJ83dAg840cetzVtEy8Rjbw1UKxgafeE8,1027
8
- pub_analyzer/css/report.tcss,sha256=5v-h4Y5gUdonLQTVkVm-HYrYT1xDS-4NYHVZDYghVEs,2066
8
+ pub_analyzer/css/report.tcss,sha256=H4lHe3yepGLRlDtQkXAx_ONCwCupacglt6j3KWQOf2Q,2027
9
9
  pub_analyzer/css/search.tcss,sha256=rovbWjp4pYfCF_OyAC_QrV_0WdMUlsYoQ3vbs9pGw7g,1326
10
10
  pub_analyzer/css/summary.tcss,sha256=i4ixICwoQFj2BToW9NVmJGUIYk5upbukbTCnDgT40ds,1350
11
11
  pub_analyzer/css/tabs.tcss,sha256=dS7y6ZZmo1Vw7Wqpx66-O-oE7zeqPE9reWqIhQ1KcZs,311
@@ -13,42 +13,42 @@ pub_analyzer/css/tree.tcss,sha256=5BSabX9ZmRL3VTz0Gya2RRJnWrwdIF9cTf6dXj2R4kE,81
13
13
  pub_analyzer/internal/__init__.py,sha256=9aqrBJDedUiBO5kEO81kSAuPbOSFoaDZZK8w5NydPhs,22
14
14
  pub_analyzer/internal/identifier.py,sha256=LDYew25TLuwqJHmLg9iRNTURWynN27ZbTxTVGbuOUD0,2939
15
15
  pub_analyzer/internal/render.py,sha256=gq5dScWs507tne3glJbTQ-PekmALvtcZPB1p_mVAkE0,2144
16
- pub_analyzer/internal/report.py,sha256=zeDn4mR8G7-NA0SxelIhanZ2e4mLecbiuVqlv2_hMb8,16320
17
- pub_analyzer/internal/templates/author/author_summary.typ,sha256=HtKVteroNhDIDu83vH3ZESm7VMmU8eMx7Wd-gqBXbHg,2827
18
- pub_analyzer/internal/templates/author/report.typ,sha256=Pq7AxT425Buz4a2NFSIoNwp4FozwuHWALzNxe0fsTwA,1749
19
- pub_analyzer/internal/templates/author/sources.typ,sha256=vY9qUEl_HbxgYl4NWR8yH0oQ5TzaYIxk3p9IsSprFBM,802
20
- pub_analyzer/internal/templates/author/works.typ,sha256=zPIzuUDJ3tL7Nf6Mt2t8W-RRQYSgWKVh4cx47APGSGs,4803
21
- pub_analyzer/internal/templates/author/works_extended.typ,sha256=kzk8gpGEqj1J-U1OrmGBGZ63N17wBwFjR1XDW6dFyWo,3508
22
- pub_analyzer/main.py,sha256=0vCh_eaPc4uaGI2WsyTp7JYc9CL5gANCaa4PZhB8Dvc,2301
16
+ pub_analyzer/internal/report.py,sha256=7T4TTe6ASQnOku0F9YVOGn3G3HmXnad0CVXvGEYQnSA,16322
17
+ pub_analyzer/internal/templates/author/author_summary.typ,sha256=nIECVqTcW_ZjHX6G8Jets_Wrg8Z1Tmm7Kz5RLD46wlQ,2836
18
+ pub_analyzer/internal/templates/author/report.typ,sha256=rlAa5hf2Ms6V3pnlVsu3aosmCwtGq3qtRjbPY9qczNA,2614
19
+ pub_analyzer/internal/templates/author/sources.typ,sha256=lQUorqZRMNI4v8P4xLvtqWy89-fbjciUturI3HrfBPo,798
20
+ pub_analyzer/internal/templates/author/works.typ,sha256=ZhJ3JhyDc4Z5JE3kLmyFJzfSNZdQi-RVlA57SxDBKIg,5132
21
+ pub_analyzer/internal/templates/author/works_extended.typ,sha256=Eh_1BONK3BArWvlo3MNQJ8kueOpW_N8PlxBTa-Guhkg,3510
22
+ pub_analyzer/main.py,sha256=tvGLwYHW-5hysM9sfaU0Il563p9zYcl6Kege--sP7hk,2427
23
23
  pub_analyzer/models/__init__.py,sha256=hvR6m379slQw7gSwnl_OFY21Ytv90mmmOe7bp8vZYkk,59
24
- pub_analyzer/models/author.py,sha256=9DzBQY4JoDv5ilBNOLyquOqnlwbXCTpmZq25cQLCgYU,1900
24
+ pub_analyzer/models/author.py,sha256=NvFmvSsmnchz2lo7m69NE3kjLYP0CXICRAolnvcznW8,2118
25
25
  pub_analyzer/models/concept.py,sha256=yNvajKWTn6uBalNoJmlobitvbFBOjF80jlZnjKjwDRw,677
26
- pub_analyzer/models/institution.py,sha256=kjS3U0w454SBCZNsZ03-XZqciZR0ubwsCRo2ix6RojM,3067
27
- pub_analyzer/models/report.py,sha256=zrAkUYgGCGnDzvQYbidyf1cmi7iXbXxsskfqZaeurbw,2574
26
+ pub_analyzer/models/institution.py,sha256=crGRWBqBBfFhLKvuyfFjTQUEnWgEkdjatTOrU2_Ahqw,3111
27
+ pub_analyzer/models/report.py,sha256=yma70xpD6I8hv_kk5ylMhylLu60eOpQwNEJa-Dd-XQI,2672
28
28
  pub_analyzer/models/source.py,sha256=o3ich4iDYB_PH_cVbrZtVRFVLQlPS3W5ajgBQQGzYqM,2730
29
29
  pub_analyzer/models/topic.py,sha256=3MBQV-njnjfmOVvgmFZxy8fFU7sMj5yxUW8EHFAjlD4,1825
30
- pub_analyzer/models/work.py,sha256=vN2mSB6oiPbkhaGy0pjWGc2meWjkkHAz5Eubed3BO6w,4115
30
+ pub_analyzer/models/work.py,sha256=ndrzbiXFUcugguZGYWqXM_BrHqmC3xII9ZtKXc_Njlw,4140
31
31
  pub_analyzer/widgets/__init__.py,sha256=JALs1yGE06XYwjoY_0AG-Wt_pMknI1WEWNYK3atQaEA,18
32
32
  pub_analyzer/widgets/author/__init__.py,sha256=oiJibt7YiuGpovOnFIAlC9YwLO-0LN3SDgPWFL-LVPQ,22
33
33
  pub_analyzer/widgets/author/cards.py,sha256=JWZxYy4Oen5fToiSBgvfEgmBJlrIVXCWpT-XjkLbxY4,2445
34
- pub_analyzer/widgets/author/core.py,sha256=XaqjOajU0zz8rtraF5Y9vjjiLyMKJmke8GblFaa6UwU,5069
35
- pub_analyzer/widgets/author/tables.py,sha256=kowgw5_NIRKdLvL9b97q9PSugHQ__eMcLWfahKETZdA,793
34
+ pub_analyzer/widgets/author/core.py,sha256=9i3U7jSeyQdAdWAslQUMd0_juw3yy9s4mEgl-nJ1LVg,5068
35
+ pub_analyzer/widgets/author/tables.py,sha256=6ZL8B0X0rCpyu_QoAEH0NVvAtTVbydXte3vQIiDui1k,1807
36
36
  pub_analyzer/widgets/body.py,sha256=wN9cMcm1MaRTjuHYt8RWrG8D_ngg5cn-hVllvmzPX_o,972
37
37
  pub_analyzer/widgets/common/__init__.py,sha256=Fx5Gl17Rd_wueZjNElBtI1kCn-4DMSbC3lEA8u7PSto,287
38
38
  pub_analyzer/widgets/common/card.py,sha256=GGSaeuZt6AqY7kAvcVnWNMrhNPzr7do66YRQOYNSYvU,595
39
39
  pub_analyzer/widgets/common/filesystem.py,sha256=i0S3D6JJzPkF1Sqm83SSQlmYFKRf82SnoFgKVE6BdYI,6460
40
- pub_analyzer/widgets/common/filters.py,sha256=7KNRcSAED0EGxIZp6o1zxmGpzbmEM2yozDeYw-5ysSM,3373
40
+ pub_analyzer/widgets/common/filters.py,sha256=bdtWaxahbFksaZZf6l0Yhgi9opH_RygFHXQV4_CYYj0,3372
41
41
  pub_analyzer/widgets/common/input.py,sha256=tK_UCtLDGHlI_NKpKjGkVu4gWiwMAIHixT9Im--Un4c,2649
42
42
  pub_analyzer/widgets/common/modal.py,sha256=otLQZotdTRTlSeTBknIxqRyduVY6lRZ5yW5u20SLcwI,882
43
43
  pub_analyzer/widgets/common/selector.py,sha256=Jh5bsn-zYmHGfEE3eO9XL6BsgKpLMGfg8FJur4gQmH0,1493
44
44
  pub_analyzer/widgets/common/summary.py,sha256=Qj-FRfAVgJmCaVUJI-jQrHX2sGKHTP2b75KukuJWlog,165
45
45
  pub_analyzer/widgets/institution/__init__.py,sha256=T_WDTDistaaq2obl1Cy_wZI5nTBiJNUnB-_OwBOLFTE,27
46
46
  pub_analyzer/widgets/institution/cards.py,sha256=OgLWP8M0xENa5NnY9NtmwjdqOwZJUN77fXSHFNT3BYU,2862
47
- pub_analyzer/widgets/institution/core.py,sha256=q21GXawR9g-pN68YgdiHc3r_ZxiKq5JYbakeLvdu4dk,5332
47
+ pub_analyzer/widgets/institution/core.py,sha256=6Tb1xQFJERswE1ra2FQFeZcQrb8zSsg-UHfPjWYUOWY,5331
48
48
  pub_analyzer/widgets/institution/tables.py,sha256=tXjrop9HGSkZGjYIOGQEOKVoyoeIGPd-8oSh08iuTRw,838
49
49
  pub_analyzer/widgets/report/__init__.py,sha256=oolRVss3JKaQHaQVDncjtxbLINRJ5Rd1ulW1uk7MLhc,54
50
- pub_analyzer/widgets/report/author.py,sha256=orlq-YSHeRcEyCXrQHiRpp2tdPC9SO1MjQ9uhNpU0-k,1227
51
- pub_analyzer/widgets/report/cards.py,sha256=NtsGCdlAqsuocun_QwE7dJeZetfAqKAjduHYpVtq-ic,4826
50
+ pub_analyzer/widgets/report/author.py,sha256=IEfRDfsA8jcmFwQQk1O-iuh8MKr4DbzBPpjoE8xECZA,1459
51
+ pub_analyzer/widgets/report/cards.py,sha256=2jf9cpfzVFZO0I9b29bkNaVhENMnfL26etEpUG-NMk0,4854
52
52
  pub_analyzer/widgets/report/concept.py,sha256=xiGXy_RXO_XmdqnlePkOozYPmQrsDdqKPMRXHsZbDP0,1485
53
53
  pub_analyzer/widgets/report/core.py,sha256=V4fwBHnSqkUzC1da8sGYqzsPKQRofA7GqpEhbEXsk4s,11436
54
54
  pub_analyzer/widgets/report/export.py,sha256=EQzF5fMZgHtLv3f5hITDgf9WW2XytRX_foeLWwcIHkM,4762
@@ -57,13 +57,13 @@ pub_analyzer/widgets/report/institution.py,sha256=PDPE9fK18l9kKKch5sJrbnHHDss0kJ
57
57
  pub_analyzer/widgets/report/locations.py,sha256=s6O5v_jX_oPsKOf2fEujtDxLHQRVsqrIcgN4rZkRKkg,2892
58
58
  pub_analyzer/widgets/report/source.py,sha256=WJhJc0_sZOcAtkmh9-VjbgugoArZgxKoXlITqVaBYK0,3045
59
59
  pub_analyzer/widgets/report/topic.py,sha256=SI3STTBFlpR-VJcsNhJyu6vc9uyytU_ASKuWXb-qr60,1969
60
- pub_analyzer/widgets/report/work.py,sha256=8PwuEkTN-cvteMv9xULLfaDrTZpogX6z0DBiOjN0JKA,9590
60
+ pub_analyzer/widgets/report/work.py,sha256=t1Z2oFB5gUIuCdUqp_vkTV9--oUfnOX-yhlCDVayla4,9592
61
61
  pub_analyzer/widgets/search/__init__.py,sha256=8C3IQtFkiIL8hlQbhJ_fAHM59-TAoe29wBAM2ptozhw,239
62
62
  pub_analyzer/widgets/search/core.py,sha256=4NvowtBcrH1fmob9kuF7v9Tq3Nd99jzB2S7xaD8OYeI,3861
63
- pub_analyzer/widgets/search/results.py,sha256=6Sl-shkGCf-jcMmalXpk1n8oBHk1aZNzFPJfHSRP1gA,3702
63
+ pub_analyzer/widgets/search/results.py,sha256=veZP5FxWFKKArH6wvxtvdu07BEYGlT7qwbRjDpgXxtU,3931
64
64
  pub_analyzer/widgets/sidebar.py,sha256=XlIshlCVW5Bb3MXFPnU9is0qQrUrGdT6xlkKiYNEcAM,2704
65
- pub_analyzer-0.4.0.dist-info/LICENSE,sha256=OPopoEowTMKqIea8Kbxk3TKdCQ97YkLvIknjTHE5oCI,1080
66
- pub_analyzer-0.4.0.dist-info/METADATA,sha256=J0vtkBiJ_KBkDg5r525pSArh9XflnHHo8FzU5gPMoyw,4508
67
- pub_analyzer-0.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
68
- pub_analyzer-0.4.0.dist-info/entry_points.txt,sha256=mVb_gUNX_-aVWHlNKLjcMAS8YLgNnSq9JLRXVJGIF2c,54
69
- pub_analyzer-0.4.0.dist-info/RECORD,,
65
+ pub_analyzer-0.4.2.dist-info/LICENSE,sha256=OPopoEowTMKqIea8Kbxk3TKdCQ97YkLvIknjTHE5oCI,1080
66
+ pub_analyzer-0.4.2.dist-info/METADATA,sha256=2m0SJ-y6nLnd4bG8Q5JNQeGqXyEyoKkOa85Gt7EkBUU,4559
67
+ pub_analyzer-0.4.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
68
+ pub_analyzer-0.4.2.dist-info/entry_points.txt,sha256=mVb_gUNX_-aVWHlNKLjcMAS8YLgNnSq9JLRXVJGIF2c,54
69
+ pub_analyzer-0.4.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any