Skip to content

Commit 4c21fa4

Browse files
Adiciona estatísticas de revisão por pares e utilitários de cálculo de datas (#1051)
* feat(dates): add get_days function and Date methods for peer review stats - Add get_days() function to calculate days between two dates - Add get_date() method to Date class with estimation support - Add get_estimated_date() method for partial dates with defaults - Add received_date and accepted_date properties to FulltextDates - Add get_peer_reviewed_stats() method for complete peer review timeline - Fix isoformat property to handle AttributeError * test(dates): add comprehensive tests for new date functions and methods - Add tests for get_days() function with various edge cases - Add tests for Date.get_date() with complete and partial dates - Add tests for Date.get_estimated_date() with custom defaults - Add tests for FulltextDates.get_peer_reviewed_stats() method - Update existing tests to match new data structure (add season field) - Import get_days function in test module * style(dates): format code and add serialize_dates option to get_peer_reviewed_stats - Apply consistent code formatting with Black - Add serialize_dates parameter to get_peer_reviewed_stats method - Convert dates to ISO format string when serialize_dates=True - Improve variable naming for clarity (received_date -> received) - Fix line spacing and indentation throughout the file - Maintain backward compatibility with default serialize_dates=False
1 parent 10b8e4b commit 4c21fa4

File tree

2 files changed

+394
-19
lines changed

2 files changed

+394
-19
lines changed

packtools/sps/models/dates.py

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,52 @@
44
from packtools.sps.models.article_and_subarticles import Fulltext
55

66

7-
class XMLWithPreArticlePublicationDateError(Exception):
8-
...
7+
class XMLWithPreArticlePublicationDateError(Exception): ...
98

109

1110
@lru_cache(maxsize=200)
1211
def format_date(year=None, month=None, day=None, **kwargs) -> str:
1312
"""
1413
Formata uma data de artigo para o formato YYYY-MM-DD.
15-
14+
1615
Args:
1716
year: Ano como string ou int
1817
month: Mês como string ou int
1918
day: Dia como string ou int
20-
19+
2120
Returns:
2221
String formatada no padrão YYYY-MM-DD
23-
22+
2423
Raises:
2524
XMLWithPreArticlePublicationDateError: Se a data for inválida
2625
"""
2726
try:
2827
# Valida a data criando um objeto date
2928
d = date(int(year), int(month), int(day))
30-
29+
3130
# Retorna a string formatada
3231
return f"{year}-{str(month).zfill(2)}-{str(day).zfill(2)}"
33-
32+
3433
except (ValueError, TypeError) as e:
3534
raise XMLWithPreArticlePublicationDateError(
3635
f"Unable to format_date "
3736
f"year={year}, month={month}, day={day}: {type(e).__name__}: {e}"
3837
)
3938

4039

40+
def get_days(start_date, end_date):
41+
"""
42+
Calculate days between two dates Date.
43+
44+
Returns:
45+
int: Number of days between start_date and end_date, or None if not available
46+
"""
47+
try:
48+
return (end_date - start_date).days
49+
except (AttributeError, TypeError):
50+
return None
51+
52+
4153
class Date:
4254
"""Represents and processes a single date from an XML node.
4355
@@ -101,8 +113,13 @@ def __str__(self):
101113

102114
@cached_property
103115
def parts(self):
104-
return {"year": self.year, "season": self.season, "month": self.month, "day": self.day}
105-
116+
return {
117+
"year": self.year,
118+
"season": self.season,
119+
"month": self.month,
120+
"day": self.day,
121+
}
122+
106123
@cached_property
107124
def display(self):
108125
if self.season:
@@ -124,11 +141,32 @@ def date(self):
124141
except (ValueError, TypeError):
125142
return None
126143

144+
def get_date(self, default_day=15, default_month=6):
145+
date_ = self.date
146+
if date_:
147+
return {"date": date_}
148+
return {
149+
"estimated": True,
150+
"date": self.get_estimated_date(
151+
default_day=default_day, default_month=default_month
152+
),
153+
}
154+
155+
def get_estimated_date(self, default_day=15, default_month=6):
156+
try:
157+
return date(
158+
int(self.year),
159+
int(self.month or default_month),
160+
int(self.day or default_day),
161+
)
162+
except (ValueError, TypeError):
163+
return None
164+
127165
@cached_property
128166
def isoformat(self):
129167
try:
130168
return self.date.isoformat()
131-
except (ValueError, TypeError):
169+
except (AttributeError, ValueError, TypeError):
132170
return None
133171

134172

@@ -239,7 +277,7 @@ def data(self):
239277
def translations(self):
240278
for node in super().translations:
241279
yield FulltextDates(node)
242-
280+
243281
@property
244282
def not_translations(self):
245283
for node in super().not_translations:
@@ -376,3 +414,86 @@ def ordered_events(self):
376414
def date_types_ordered_by_date(self):
377415
# obtem uma lista com os nomes dos eventos ordenados
378416
return [event[0] for event in self.ordered_events]
417+
418+
@cached_property
419+
def received_date(self):
420+
"""Get received date as a Date instance.
421+
422+
Returns:
423+
Date: Date instance for the received date, or None if not available
424+
"""
425+
try:
426+
node = self.front.xpath(".//history//date[@date-type='received']")[0]
427+
return Date(node)
428+
except (IndexError, AttributeError):
429+
return None
430+
431+
@cached_property
432+
def accepted_date(self):
433+
"""Get accepted date as a Date instance.
434+
435+
Returns:
436+
Date: Date instance for the accepted date, or None if not available
437+
"""
438+
try:
439+
node = self.front.xpath(".//history//date[@date-type='accepted']")[0]
440+
return Date(node)
441+
except (IndexError, AttributeError):
442+
return None
443+
444+
def get_peer_reviewed_stats(
445+
self, default_month=6, default_day=15, serialize_dates=False
446+
):
447+
received = {}
448+
if self.received_date:
449+
received = self.received_date.get_date(
450+
default_month=default_month, default_day=default_day
451+
)
452+
accepted = {}
453+
if self.accepted_date:
454+
accepted = self.accepted_date.get_date(
455+
default_month=default_month, default_day=default_day
456+
)
457+
published = {}
458+
if self.epub_date_model or self.collection_date_model:
459+
published = (self.epub_date_model or self.collection_date_model).get_date(
460+
default_month=default_month, default_day=default_day
461+
)
462+
463+
received_date = received.get("date")
464+
accepted_date = accepted.get("date")
465+
published_date = published.get("date")
466+
467+
stats = {}
468+
stats["received_date"] = (
469+
received_date.isoformat()
470+
if serialize_dates and received_date
471+
else received_date
472+
)
473+
stats["accepted_date"] = (
474+
accepted_date.isoformat()
475+
if serialize_dates and accepted_date
476+
else accepted_date
477+
)
478+
stats["published_date"] = (
479+
published_date.isoformat()
480+
if serialize_dates and published_date
481+
else published_date
482+
)
483+
stats["days_from_received_to_accepted"] = get_days(received_date, accepted_date)
484+
stats["estimated_days_from_received_to_accepted"] = received.get(
485+
"estimated"
486+
) or accepted.get("estimated")
487+
stats["days_from_accepted_to_published"] = get_days(
488+
accepted_date, published_date
489+
)
490+
stats["estimated_days_from_accepted_to_published"] = accepted.get(
491+
"estimated"
492+
) or published.get("estimated")
493+
stats["days_from_received_to_published"] = get_days(
494+
received_date, published_date
495+
)
496+
stats["estimated_days_from_received_to_published"] = received.get(
497+
"estimated"
498+
) or published.get("estimated")
499+
return stats

0 commit comments

Comments
 (0)