feat: enrichment Hub API, feature flags UI/gating, NCM refresher, Agent enrichment worker with tests

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-25 18:47:35 -03:00
co-authored by Claude Sonnet 5
parent 27f992d9e0
commit 5deebea14e
22 changed files with 1118 additions and 23 deletions
+15 -1
View File
@@ -36,6 +36,7 @@ from gti_fiscal_agent.pipeline import run_pipeline # noqa: E402
from gti_fiscal_agent.runner import REFERENCE_SCHEMA_V1, product_fingerprint, to_hub_product # noqa: E402
from central.app.operational_state import classify_product_state, summarize_operational_states # noqa: E402
from gti_fiscal_agent.state import AgentState # noqa: E402
from gti_fiscal_agent import enrichment as _enrichment # noqa: E402
_MAX_BACKOFF_SECONDS = 60
SYNC_JOB_KINDS = ("INITIAL_SYNC", "SYNC_INCREMENTAL", "REPROCESS_ALL") # read-only ERP jobs that refresh coverage
@@ -409,6 +410,17 @@ def resolve_installation_id(config, env, state, client):
return state.installation_id(str(installation_id))
def _start_enrichment(config, env, client, stopper):
"""New-product enrichment worker (own thread, own SQLite file). It gates ITSELF on the Hub's per-installation features: DISABLED / not configured = zero Firebird activity."""
if not hasattr(client, "resolve_product"):
return None
store = _enrichment.EnrichmentStore(Path(config.state_db).with_name("enrichment_state.db"))
worker = _enrichment.EnrichmentWorker(hub=client, store=store, read_conn=lambda: _firebird_reader._connect(env, True), write_conn=lambda: open_write_connection(env, config.database_path),
create_reprocess=lambda codes: client.create_job("REPROCESS_PRODUCTS", {"codigos": list(codes), "reason": "ENRICHMENT"}))
threading.Thread(target=worker.run_forever, args=(stopper,), name="product-enrichment", daemon=True).start()
return worker
def run(config, env, *, max_iterations=None, stopper=None):
"""`stopper` defaults to a signal-based GracefulStop (SIGINT/SIGTERM), which
only works from the main thread. A host that manages its own lifecycle
@@ -420,6 +432,8 @@ def run(config, env, *, max_iterations=None, stopper=None):
client = HubClient(config.hub_url, config.agent_token)
installation_id = resolve_installation_id(config, env, state, client)
stopper = stopper if stopper is not None else GracefulStop()
if max_iterations is None:
_start_enrichment(config, env, client, stopper)
backoff = 1
was_down = False
@@ -436,7 +450,7 @@ def run(config, env, *, max_iterations=None, stopper=None):
"agent_version": config.agent_version, "last_job_id": last_job_id, "display_name": env.get("AGENT_DISPLAY_NAME", ""),
"customer_code": env.get("CUSTOMER_CODE"), "hostname": env.get("AGENT_HOSTNAME") or os.environ.get("COMPUTERNAME"),
"windows_version": env.get("WINDOWS_VERSION"), "company_code": config.company_id, "company_uf": config.company_uf,
"database_path": config.database_path, "capabilities": {"heartbeat": True, "jobs": True, "local_logs": True, "RTC_CCLASSTRIB_WRITE_V1": True},
"database_path": config.database_path, "capabilities": {"heartbeat": True, "jobs": True, "local_logs": True, "RTC_CCLASSTRIB_WRITE_V1": True, "PRODUCT_ENRICHMENT_WRITE_V1": True},
"fiscal_engine_version": env.get("FISCAL_ENGINE_VERSION", config.agent_version), "dataset_versions": {},
}
if hasattr(client, "heartbeat_metadata"):