Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6aeab55
Corrigir extração de dados do XML para tolerar mudanças de versão do …
robertatakenaka Aug 29, 2026
7930637
Usar as funções de compatibilidade do packtools em PidProviderXML
robertatakenaka Aug 29, 2026
b5f488d
Realinhar testes de QueryBuilderPidProviderXML com o comportamento re…
robertatakenaka Aug 29, 2026
eba94cf
Ajustar asserção de select_record para o novo formato de dados de com…
robertatakenaka Aug 29, 2026
e785043
Documentar funções e atributos de query_params.py
robertatakenaka Aug 29, 2026
0ab5803
Corrige o acesso a z_partial_body
robertatakenaka Aug 30, 2026
3e610bf
Ajusta comparação de dados para maior precisão no pareamento de XML
robertatakenaka Aug 31, 2026
89aeeba
Aprimora rastreabilidade e robustez do processo de pareamento de PidP…
robertatakenaka Aug 31, 2026
0ec40e7
Adiciona testes de configuração do Pid Provider
robertatakenaka Aug 31, 2026
714ace9
Adiciona testes de XMLVersion
robertatakenaka Aug 31, 2026
3926dd5
Adiciona testes de URL de XML no Pid Provider
robertatakenaka Aug 31, 2026
a3b0cf5
Adiciona testes de PIDs alternativos (other_pid)
robertatakenaka Aug 31, 2026
28706d9
Adiciona testes de correção do pid v2
robertatakenaka Aug 31, 2026
d9d8cfa
Adiciona testes de funções auxiliares do PidProviderXML
robertatakenaka Aug 31, 2026
fefbac1
Adiciona testes de manutenção de registros PidProviderXML
robertatakenaka Aug 31, 2026
d100d5b
Adiciona testes de auditoria de registro (PidProviderXMLRegistration)
robertatakenaka Aug 31, 2026
7cc384f
Atualiza testes de query_params para tratamento de labels ausentes e …
robertatakenaka Aug 31, 2026
5cd682a
Atualiza testes de get_best_match para novo formato de item com data/…
robertatakenaka Aug 31, 2026
824b21e
Atualiza testes de select_record para uso de fix_get_data_to_compare
robertatakenaka Aug 31, 2026
7554bf6
Atualiza testes de register() para a migração readable_data
robertatakenaka Aug 31, 2026
c576379
Corrige estrutura dos dados legíveis no dict de registro do PID
robertatakenaka Aug 31, 2026
c0bf3a9
Adiciona pid_provider/tests/test_models.py
robertatakenaka Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 70 additions & 28 deletions pid_provider/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
zero_to_none,
compare,
QueryBuilderPidProviderXML,
fix_get_article_data,
fix_get_data_to_compare,
)
from tracker.models import BaseEvent, UnexpectedEvent

Expand Down Expand Up @@ -615,15 +617,33 @@ def data(self):
"v2": self.v2,
"aop_pid": self.aop_pid,
"pkg_name": self.pkg_name,
"finger_print": self.current_version and self.current_version.finger_print,
"created": self.created and self.created.isoformat(),
"updated": self.updated and self.updated.isoformat(),
"record_status": "updated" if self.updated else "created",
"registered_in_core": self.registered_in_core,
"ppx_id": self.id
}
_data.update(self.get_readable_data())
_data["registered_data"] = self.get_readable_data()
_data.update(self.record_status)
return _data

@property
def record_status(self):
"""Retorna os timestamps e o estado do registro ('created' ou 'updated').
Calcula a variação entre `created` e `updated` para definir o estado de
persistência. Utilizado para direcionar o código HTTP de resposta:
- 'created' -> 201 Created
- 'updated' -> 200 OK ou 204 No Content
Returns:
dict: Dicionário com `created`, `updated` em ISO 8601 e `record_status`.
"""
d = {}
if self.created:
d["created"] = self.created.isoformat()
d["record_status"] = "created"
if self.updated:
d["updated"] = self.updated.isoformat()
if (self.updated - self.created).total_seconds() > 1:
d["record_status"] = "updated"
return d

@classmethod
@profile_classmethod
def get_xml_with_pre(cls, v3):
Expand Down Expand Up @@ -657,25 +677,35 @@ def is_aop(self):
return True

def get_readable_data(self):
if self.readable_data:
return self.readable_data
readable_data = self.readable_data or {}
if readable_data:
try:
readable_data.pop("partial_body")
except KeyError:
pass
return readable_data
if self.xml_with_pre:
return self.xml_with_pre.get_article_data()
return fix_get_article_data(self.xml_with_pre)
return {}

@property
def data_to_compare(self):
data = {}
readable = self.get_readable_data()
titles = readable.get("article_titles")
body_fragment = readable.get("body_fragment")
return {
"article_titles": titles or self.xml_with_pre.article_titles_texts,
if readable:
titles = readable.get("article_titles")
body_fragment = readable.get("body_fragment")
if titles:
data["article_titles"] = titles
if body_fragment:
data["body_fragment"] = body_fragment
data.update({
"z_surnames": self.z_surnames,
"z_collab": self.z_collab,
"z_links": self.z_links,
"z_partial_body": self.z_partial_body,
"body_fragment": body_fragment or self.xml_with_pre.get_body_fragment(PARTIAL_BODY_MAX),
}
"body_fragment_fingerprint": self.z_partial_body,
})
return data

@classmethod
@profile_classmethod
Expand Down Expand Up @@ -758,7 +788,7 @@ def register(

input_data = {}
input_data.update(xml_with_pre.data)
input_data.update(xml_with_pre.get_article_data())
input_data.update(fix_get_article_data(xml_with_pre))
input_data["origin"] = origin
response["input_data"] = input_data

Expand Down Expand Up @@ -997,6 +1027,9 @@ def is_updated(
)
return

if not registered.readable_data:
return

# verifica se é necessário atualizar
if registered.is_equal_to(xml_with_pre):
# XML fornecido é igual ao registrado, não precisa continuar
Expand Down Expand Up @@ -1074,12 +1107,12 @@ def select_record(xml_adapter, selection_results):
checamos truthiness (nunca .exists()/.count() sobre queryset).
"""
unmatched_items = {}
xml_adapter_data_to_compare = xml_adapter.get_data_to_compare()
xml_adapter_data_to_compare = fix_get_data_to_compare(xml_adapter)
for label, results in selection_results:
if not results:
continue

result = PidProviderXML.get_best_match(results, xml_adapter_data_to_compare)
result = PidProviderXML.get_best_match(results, xml_adapter_data_to_compare)

matched = result.get("matched")
unmatched = result.get("unmatched")
Expand Down Expand Up @@ -1117,14 +1150,15 @@ def get_record_by_pid_v3(cls, xml_adapter):
# pid v3 é inédito
raise cls.DoesNotExist

xml_adapter_data_to_compare = xml_adapter.get_data_to_compare()
xml_adapter_data_to_compare = fix_get_data_to_compare(xml_adapter)
result = PidProviderXML.get_best_match(results, xml_adapter_data_to_compare)

registered = result.get("registered")
if not registered:
xml_data = xml_adapter.xml_with_pre.get_article_data(PARTIAL_BODY_MAX)
items = [item.data for item in results]
raise PidProviderXMLPidV3ConflictError(
_(f"{xml_pid_v3} belongs to {items}, not to {xml_data}")
_("{} do not belong to {}. Result: {}").format(
xml_pid_v3, xml_adapter_data_to_compare, result,
)
)
return registered

Expand All @@ -1150,10 +1184,10 @@ def get_best_match(results, xml_adapter_data):
ao consumir o retorno, nunca acesso direto.

- ``"unmatched"``: presente apenas se houver ao menos 1
candidato com ``percentual_score`` <= 0.6. Lista de
candidato com ``percentual_score`` <= min_rate. Lista de
``item.data`` desses candidatos.
- ``"registered"``: presente apenas se houver ao menos 1
candidato aprovado (score > 0.6). Contém o OBJETO
candidato aprovado (score > min_rate). Contém o OBJETO
``PidProviderXML`` (não o dict ``.data``) do candidato com
maior score — em caso de empate, o critério de desempate é
``updated`` mais recente e, em seguida, maior ``id``.
Expand All @@ -1166,20 +1200,26 @@ def get_best_match(results, xml_adapter_data):
detail = {}
found = []
items = {}
responses = {}
min_rate = 0.6
if len(xml_adapter_data) <= 4:
min_rate = 0.49
for item in results:
item_data = item.data_to_compare
response = compare(item_data, xml_adapter_data)
items[item.id] = item
responses[item.id] = response
found.append((response["percentual_score"], item.updated.isoformat(), item.id))

found = sorted(found, reverse=True)
matched = []
unmatched = []
for percentual_score, updated, item_id in found:
if percentual_score > 0.6:
matched.append(items[item_id].data)
data = {"data": items[item_id].data, "response": responses[item_id]}
if percentual_score > min_rate:
matched.append(data)
else:
unmatched.append(items[item_id].data)
unmatched.append(data)
if matched:
detail["registered"] = items[found[0][-1]]
if len(matched) > 1:
Expand Down Expand Up @@ -1210,7 +1250,7 @@ def _add_data(self, xml_adapter, registered_in_core):
self.z_links = xml_adapter.z_links
self.z_partial_body = xml_adapter.xml_with_pre.body_fragment_fingerprint

self.readable_data = xml_adapter.xml_with_pre.get_article_data()
self.readable_data = fix_get_article_data(xml_adapter.xml_with_pre)

@profile_method
def _add_dates(self, xml_adapter, origin_date, available_since):
Expand Down Expand Up @@ -1382,6 +1422,8 @@ def is_registered(
response["registered"] = True
response.update(registered.data)
response["is_equal"] = registered.is_equal_to(xml_with_pre)
if not registered.readable_data:
response["is_equal"] = False
return response
except Exception as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
Expand Down
Loading
Loading