Files
Jyotisha/scripts/vedastro_service_adapter.py
T
2026-06-29 07:51:00 +08:00

1211 lines
43 KiB
Python

#!/usr/bin/env python3
"""Minimal VedAstro service-boundary adapter skeleton.
This module does not replace the local SwissEph path. It only defines the
request/response schema and a controlled "not configured" status so the
workspace can evolve from research notes to an executable adapter contract.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import socket
import time
from pathlib import Path
from typing import Any
from urllib import request, error
from urllib.parse import urlparse
ROOT = Path(__file__).resolve().parents[1]
PARITY_CASES = {
"user_REDACTED_YEAR_test": {
"year": REDACTED_YEAR,
"month": 4,
"day": 17,
"hour": 14,
"minute": 49,
"lat": 36.42,
"lon": 114.2,
"tz": 8.0,
"ayanamsa_policy": "lahiri",
"node_policy": "mean",
},
"beijing_first_use_demo": {
"year": 1990,
"month": 1,
"day": 1,
"hour": 12,
"minute": 0,
"lat": 39.9042,
"lon": 116.4074,
"tz": 8.0,
"ayanamsa_policy": "lahiri",
"node_policy": "mean",
},
"delhi_lagna_boundary": {
"year": 1984,
"month": 10,
"day": 31,
"hour": 6,
"minute": 30,
"lat": 28.6139,
"lon": 77.2090,
"tz": 5.5,
"ayanamsa_policy": "lahiri",
"node_policy": "mean",
},
"new_york_moon_boundary": {
"year": 2001,
"month": 9,
"day": 11,
"hour": 8,
"minute": 46,
"lat": 40.7128,
"lon": -74.0060,
"tz": -4.0,
"ayanamsa_policy": "lahiri",
"node_policy": "mean",
},
}
SUPPORTED_RANGE_SCAN_DOMAINS = {"marriage", "wealth", "career"}
SUPPORTED_EXTERNAL_TECHNIQUE_DOMAINS = {"marriage", "wealth", "career", "general"}
OFFICIAL_SEARCH_EVENTS_ENDPOINT_PATH = "/Calculate/SearchEvents"
OFFICIAL_SEARCH_EVENTS_METHOD = "POST"
OFFICIAL_SEARCH_EVENTS_PROFILE_VERSION = "official_builder_search_events_v1"
OFFICIAL_RANGE_SCAN_EVENT_TAGS = {
"marriage": ["Marriage", "Personal", "General"],
"wealth": ["LendingMoney", "BorrowingMoney", "BuyingSelling", "General"],
"career": ["Personal", "General", "Building", "Travel"],
}
VEDASTRO_CALCULATION_COVERAGE = {
"official_python_library_calculations": "596+",
"official_api_builder_calculators": "600+",
"official_events_builder_events": "400+",
"official_events_builder_methods": ["SearchEvents", "GetEventTiming", "ListEventTypes"],
"range_scan_role": "high_frequency_life_event_radar",
"intended_use": "external_timing_evidence_for_strict_workflow",
}
EXTERNAL_TECHNIQUE_ROLE = "external_technique_evidence"
EXTERNAL_TECHNIQUE_OPERATION = "calculation_method"
EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY = {
"role": EXTERNAL_TECHNIQUE_ROLE,
"can_change_score": False,
"can_set_dominant_label": False,
"can_set_payout_label": False,
"allowed_destinations": ["secondary_context", "technique_audit"],
}
RANGE_SCAN_EVENT_ALLOWLIST = {
"marriage": {
"event_ids": {
"GocharJupiterIn7th",
"GocharJupiterAspect7th",
"GocharSaturnAspect7th",
"JupiterSupportsMarriageAxis",
},
"tags": {"marriage", "relationship", "spouse", "transit"},
},
"wealth": {
"event_ids": {
"GocharJupiterIn2nd",
"GocharJupiterIn11th",
"GocharJupiterAspect2nd",
"GocharJupiterAspect11th",
"WealthExpansionWindow",
},
"tags": {"wealth", "finance", "income", "gains", "transit"},
},
"career": {
"event_ids": {
"GocharJupiterIn10th",
"GocharSaturnIn10th",
"GocharJupiterAspect10th",
"CareerExpansionWindow",
},
"tags": {"career", "profession", "work", "transit"},
},
}
RANGE_SCAN_SIGNAL_METADATA = {
"marriage": {
"GocharJupiterIn7th": {
"signal_key": "gochar_jupiter_7th_marriage",
"signal_label": "Jupiter in 7th marriage window",
"signal_family": "marriage_trigger",
},
"GocharJupiterAspect7th": {
"signal_key": "gochar_jupiter_aspect_7th_marriage",
"signal_label": "Jupiter aspecting 7th marriage window",
"signal_family": "marriage_trigger",
},
"GocharSaturnAspect7th": {
"signal_key": "gochar_saturn_aspect_7th_relationship_pressure",
"signal_label": "Saturn aspecting 7th relationship window",
"signal_family": "relationship_pressure",
},
"JupiterSupportsMarriageAxis": {
"signal_key": "jupiter_supports_marriage_axis",
"signal_label": "Jupiter supports marriage axis",
"signal_family": "marriage_trigger",
},
},
"wealth": {
"GocharJupiterIn2nd": {
"signal_key": "gochar_jupiter_2nd_wealth",
"signal_label": "Jupiter in 2nd wealth window",
"signal_family": "wealth_trigger",
},
"GocharJupiterIn11th": {
"signal_key": "gochar_jupiter_11th_gains",
"signal_label": "Jupiter in 11th gains window",
"signal_family": "gains_trigger",
},
"GocharJupiterAspect2nd": {
"signal_key": "gochar_jupiter_aspect_2nd_wealth",
"signal_label": "Jupiter aspecting 2nd wealth window",
"signal_family": "wealth_trigger",
},
"GocharJupiterAspect11th": {
"signal_key": "gochar_jupiter_aspect_11th_gains",
"signal_label": "Jupiter aspecting 11th gains window",
"signal_family": "gains_trigger",
},
"WealthExpansionWindow": {
"signal_key": "wealth_expansion_window",
"signal_label": "Wealth expansion window",
"signal_family": "wealth_trigger",
},
},
"career": {
"GocharJupiterIn10th": {
"signal_key": "gochar_jupiter_10th_career",
"signal_label": "Jupiter in 10th career window",
"signal_family": "career_trigger",
},
"GocharSaturnIn10th": {
"signal_key": "gochar_saturn_10th_career",
"signal_label": "Saturn in 10th career window",
"signal_family": "career_pressure",
},
"GocharJupiterAspect10th": {
"signal_key": "gochar_jupiter_aspect_10th_career",
"signal_label": "Jupiter aspecting 10th career window",
"signal_family": "career_trigger",
},
"CareerExpansionWindow": {
"signal_key": "career_expansion_window",
"signal_label": "Career expansion window",
"signal_family": "career_trigger",
},
},
}
RANGE_SCAN_OFFICIAL_TAG_MATCHES = {
"marriage": {"Marriage"},
"wealth": {"LendingMoney", "BorrowingMoney", "BuyingSelling"},
"career": {"Building", "Travel"},
}
RANGE_SCAN_ALIAS_TERMS = {
"marriage": {
"marriage",
"spouse",
"wedding",
"relationship",
"partner",
"partnership",
},
"wealth": {
"wealth",
"money",
"finance",
"financial",
"income",
"gain",
"gains",
"lending",
"borrowing",
"business",
"cash",
},
"career": {
"career",
"profession",
"work",
"job",
"business",
"travel",
"building",
"public",
"status",
},
}
MATCH_METADATA_BY_TYPE = {
"exact_id": {"signal_lift": 3, "confidence": "high"},
"official_tag": {"signal_lift": 2, "confidence": "medium_high"},
"alias": {"signal_lift": 1, "confidence": "low"},
"rejected": {"signal_lift": 0, "confidence": "rejected"},
}
ALIAS_NEGATIVE_GUARD_TERMS = {"noise", "without", "generic", "irrelevant", "insignificance", "not"}
DEFAULT_TIMEOUT_SECONDS = 120
TIMEOUT_ENV = "VEDASTRO_TIMEOUT_SECONDS"
BACKOFF_ENV = "VEDASTRO_RETRY_BACKOFF_SECONDS"
RETRY_POLICY = {
"max_attempts": 2,
"backoff_seconds": 1,
"retry_on": ["timeout", "429", "502", "503", "504"],
}
ALLOW_NETWORK_ENV = "VEDASTRO_ENABLE_NETWORK"
ARTIFACT_DIR = ROOT / "scratch" / "local" / "vedastro_adapter"
def _timeout_seconds() -> float:
raw = os.environ.get(TIMEOUT_ENV, "").strip()
if not raw:
return DEFAULT_TIMEOUT_SECONDS
try:
return float(raw)
except ValueError:
return DEFAULT_TIMEOUT_SECONDS
def _backoff_seconds() -> float:
raw = os.environ.get(BACKOFF_ENV, "").strip()
if not raw:
return float(RETRY_POLICY["backoff_seconds"])
try:
return max(0.0, float(raw))
except ValueError:
return float(RETRY_POLICY["backoff_seconds"])
def _json_bytes(payload: dict[str, Any]) -> bytes:
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
def _hash_payload(payload: dict[str, Any]) -> str:
return hashlib.sha256(_json_bytes(payload)).hexdigest()
def _utc_timestamp() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def _endpoint_host(endpoint: str) -> str:
parsed = urlparse(endpoint)
return parsed.netloc or endpoint
def _artifact_path(operation: str, request_hash: str, response_hash: str) -> Path:
ARTIFACT_DIR.mkdir(parents=True, exist_ok=True)
filename = f"{operation}-{request_hash[:12]}-{response_hash[:12]}.json"
return ARTIFACT_DIR / filename
def _repo_relative(path: Path) -> str:
try:
return str(path.relative_to(ROOT))
except ValueError:
return str(path)
def _write_artifact(result: dict[str, Any]) -> str:
metadata = result.get("source_metadata") or {}
artifact = _artifact_path(
str(metadata.get("operation") or result.get("operation") or "calculation"),
str(metadata.get("request_hash") or "no-request-hash"),
str(metadata.get("response_hash") or "no-response-hash"),
)
artifact.write_text(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
return _repo_relative(artifact)
def schema() -> dict[str, Any]:
request_example = {
**PARITY_CASES["beijing_first_use_demo"],
"body_list": ["Sun", "Moon", "Ascendant", "Rahu", "Ketu"],
}
range_scan_allowlist = {
domain: {
"event_ids": sorted(values["event_ids"]),
"tags": sorted(values["tags"]),
}
for domain, values in sorted(RANGE_SCAN_EVENT_ALLOWLIST.items())
}
return {
"adapter": "vedastro_service_adapter",
"backend": "vedastro_service_adapter_candidate",
"transport": "http_json_service_boundary",
"default_timeout_seconds": DEFAULT_TIMEOUT_SECONDS,
"retry_policy": RETRY_POLICY,
"required_env": {
"endpoint": "VEDASTRO_API_ENDPOINT",
"api_key_optional": "VEDASTRO_API_KEY",
},
"request_contract": [
"year",
"month",
"day",
"hour",
"minute",
"lat",
"lon",
"tz",
"ayanamsa_policy",
"node_policy",
"body_list",
],
"response_contract": [
"backend",
"available",
"status",
"ayanamsa_value",
"node_policy",
"body_list",
"bodies",
"source_metadata",
],
"range_scan_request_contract": [
"operation",
"vedastro_event_method",
"domain",
"start_date",
"end_date",
"year",
"month",
"day",
"hour",
"minute",
"lat",
"lon",
"tz",
"ayanamsa_policy",
"node_policy",
"event_model",
],
"range_scan_response_contract": [
"backend",
"available",
"status",
"operation",
"domain",
"evidence_ledger",
"source_metadata",
],
"official_search_events_profile_contract": {
"profile_version": OFFICIAL_SEARCH_EVENTS_PROFILE_VERSION,
"base_url_requirement": "VEDASTRO_API_ENDPOINT must end with /api",
"route_template": OFFICIAL_SEARCH_EVENTS_ENDPOINT_PATH,
"method": OFFICIAL_SEARCH_EVENTS_METHOD,
"content_type": "application/json",
"optional_auth_header": "x-api-key",
"body_fields": [
"BirthTime",
"Ayanamsa",
"EventTagList",
],
"range_mode_fields": [
"AtTime | StartTime + EndTime + PrecisionHours",
],
},
"vedastro_calculation_coverage": VEDASTRO_CALCULATION_COVERAGE,
"external_technique_request_contract": [
"operation",
"role",
"domain",
"method",
"api_endpoint",
"year",
"month",
"day",
"hour",
"minute",
"lat",
"lon",
"tz",
"ayanamsa_policy",
"node_policy",
],
"external_technique_response_contract": [
"backend",
"available",
"status",
"operation",
"role",
"domain",
"evidence_ledger",
"adjudicator_policy",
"source_metadata",
],
"external_technique_adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
"range_scan_event_allowlist": range_scan_allowlist,
"request_example": request_example,
"provenance_contract": {
"external_service": True,
"required_fields": [
"endpoint",
"endpoint_host",
"transport",
"provenance_mode",
"retry_policy",
"timeout_seconds",
"request_hash",
"response_hash",
"called_at",
"artifact_path",
],
},
}
def _unconfigured(reason: str) -> dict[str, Any]:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "service_endpoint_not_configured",
"reason": reason,
"source_metadata": {
"transport": "http_json_service_boundary",
"endpoint_env": "VEDASTRO_API_ENDPOINT",
"api_key_env": "VEDASTRO_API_KEY",
"provenance_mode": "external_service_candidate",
"timeout_seconds": _timeout_seconds(),
"retry_policy": RETRY_POLICY,
},
}
def _request_preview(case: dict[str, Any]) -> dict[str, Any]:
return {
**case,
"body_list": ["Sun", "Moon", "Ascendant", "Rahu", "Ketu"],
}
def _range_scan_preview(case: dict[str, Any], domain: str, start_date: str, end_date: str) -> dict[str, Any]:
preview = {
"operation": "range_scan",
"vedastro_event_method": "SearchEvents",
"domain": domain,
"start_date": start_date,
"end_date": end_date,
"event_model": "vedastro_events_at_range_candidate",
**case,
}
preview["official_request_profile"] = _build_official_search_events_profile(preview)
return preview
def _format_std_time(date_text: str, hour: Any, minute: Any, tz: Any) -> str:
year, month, day = str(date_text).split("-")
hour_int = int(float(hour))
minute_int = int(float(minute))
return f"{hour_int:02d}:{minute_int:02d} {day}/{month}/{year} {tz}"
def _time_json_from_case(case: dict[str, Any], date_text: str) -> dict[str, Any]:
return {
"StdTime": _format_std_time(date_text, case.get("hour", 0), case.get("minute", 0), case.get("tz", "+00:00")),
"Location": {
"Name": case.get("case_id") or "UserLocation",
"Latitude": case.get("lat"),
"Longitude": case.get("lon"),
},
}
def _normalize_tz(case: dict[str, Any]) -> str:
tz = case.get("tz")
if isinstance(tz, str):
return tz
if tz is None:
return "+00:00"
sign = "+" if float(tz) >= 0 else "-"
value = abs(float(tz))
hours = int(value)
minutes = int(round((value - hours) * 60))
return f"{sign}{hours:02d}:{minutes:02d}"
def _build_official_search_events_profile(request_preview: dict[str, Any]) -> dict[str, Any]:
case = dict(request_preview)
case["tz"] = _normalize_tz(case)
body = {
"BirthTime": _time_json_from_case(case, f"{case['year']:04d}-{case['month']:02d}-{case['day']:02d}"),
"Ayanamsa": str(case.get("ayanamsa_policy") or "lahiri"),
"EventTagList": OFFICIAL_RANGE_SCAN_EVENT_TAGS.get(str(request_preview.get("domain") or ""), ["General"]),
}
start_time = _time_json_from_case(case, str(request_preview["start_date"]))
end_time = _time_json_from_case(case, str(request_preview["end_date"]))
if str(request_preview["start_date"]) == str(request_preview["end_date"]):
body["AtTime"] = start_time
else:
body["StartTime"] = start_time
body["EndTime"] = end_time
body["PrecisionHours"] = 100
headers: dict[str, str] = {"Content-Type": "application/json"}
api_key = os.environ.get("VEDASTRO_API_KEY", "").strip()
if api_key:
headers["x-api-key"] = api_key
return {
"profile_version": OFFICIAL_SEARCH_EVENTS_PROFILE_VERSION,
"endpoint_path": OFFICIAL_SEARCH_EVENTS_ENDPOINT_PATH,
"method": OFFICIAL_SEARCH_EVENTS_METHOD,
"headers": headers,
"body": body,
}
def _external_technique_preview(
case: dict[str, Any],
domain: str,
method: str,
api_endpoint: str,
) -> dict[str, Any]:
return {
"operation": EXTERNAL_TECHNIQUE_OPERATION,
"role": EXTERNAL_TECHNIQUE_ROLE,
"domain": domain,
"method": method,
"api_endpoint": api_endpoint,
**case,
}
def _base_live_metadata(
endpoint: str,
request_preview: dict[str, Any],
payload: dict[str, Any],
operation: str,
attempt_count: int = 1,
retry_error_codes: list[int] | None = None,
) -> dict[str, Any]:
official_request_profile = request_preview.get("official_request_profile") if isinstance(request_preview, dict) else None
metadata = {
"transport": "http_json_service_boundary",
"endpoint": endpoint,
"endpoint_host": _endpoint_host(endpoint),
"method": "POST",
"operation": operation,
"provenance_mode": "external_service_candidate",
"timeout_seconds": _timeout_seconds(),
"retry_policy": {**RETRY_POLICY, "backoff_seconds": _backoff_seconds()},
"network_execution_env": ALLOW_NETWORK_ENV,
"called_at": _utc_timestamp(),
"request_hash": _hash_payload(request_preview),
"response_hash": _hash_payload(payload),
"attempt_count": attempt_count,
"retry_error_codes": retry_error_codes or [],
}
if isinstance(official_request_profile, dict):
redacted_headers = dict(official_request_profile.get("headers") or {})
if "x-api-key" in redacted_headers:
redacted_headers["x-api-key"] = "[redacted]"
redacted_profile = {
**official_request_profile,
"headers": redacted_headers,
}
metadata["official_endpoint_path"] = official_request_profile.get("endpoint_path")
metadata["official_request_profile"] = redacted_profile
metadata["official_request_profile_hash"] = _hash_payload(redacted_profile)
return metadata
def _normalize_success(
payload: dict[str, Any],
endpoint: str,
request_preview: dict[str, Any],
attempt_count: int = 1,
retry_error_codes: list[int] | None = None,
) -> dict[str, Any]:
metadata = {
**_base_live_metadata(endpoint, request_preview, payload, "calculation", attempt_count, retry_error_codes),
**(payload.get("source_metadata") or {}),
}
result = {
"backend": "vedastro_service_adapter_candidate",
"available": True,
"status": "ok",
"ayanamsa_value": payload.get("ayanamsa_value"),
"node_policy": payload.get("node_policy"),
"body_list": payload.get("body_list"),
"bodies": payload.get("bodies"),
"source_metadata": metadata,
}
result["source_metadata"]["artifact_path"] = _write_artifact(result)
return result
def _normalize_external_technique_success(
payload: dict[str, Any],
endpoint: str,
request_preview: dict[str, Any],
) -> dict[str, Any]:
evidence = {
"source": "vedastro_service_adapter_candidate",
"operation": EXTERNAL_TECHNIQUE_OPERATION,
"role": EXTERNAL_TECHNIQUE_ROLE,
"domain": request_preview["domain"],
"method": request_preview["method"],
"api_endpoint": request_preview["api_endpoint"],
"status": payload.get("status") or "ok",
"summary": payload.get("summary"),
"nature": payload.get("nature"),
"tags": payload.get("tags") if isinstance(payload.get("tags"), list) else [],
"raw": payload,
}
return {
"backend": "vedastro_service_adapter_candidate",
"available": True,
"status": "ok",
"operation": EXTERNAL_TECHNIQUE_OPERATION,
"role": EXTERNAL_TECHNIQUE_ROLE,
"domain": request_preview["domain"],
"request_preview": request_preview,
"evidence_ledger": [evidence],
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
"source_metadata": {
"transport": "http_json_service_boundary",
"endpoint": endpoint,
"provenance_mode": "external_service_candidate",
"timeout_seconds": _timeout_seconds(),
"retry_policy": RETRY_POLICY,
**(payload.get("source_metadata") or {}),
},
}
def _normalize_range_scan_success(
payload: dict[str, Any],
endpoint: str,
request_preview: dict[str, Any],
attempt_count: int = 1,
retry_error_codes: list[int] | None = None,
) -> dict[str, Any]:
# Handle actual VedAstro response format: {"Status": "Pass", "Payload": [...]}
if payload.get("Status") == "Pass":
events = payload.get("Payload", [])
else:
# Fallback to local stub format if not VedAstro format
events = payload.get("events", [])
if not isinstance(events, list):
events = []
domain = request_preview.get("domain", "")
allowlist = RANGE_SCAN_EVENT_ALLOWLIST.get(domain, {})
allowed_ids = allowlist.get("event_ids", set())
allowed_tags = allowlist.get("tags", set())
official_tags = RANGE_SCAN_OFFICIAL_TAG_MATCHES.get(domain, set())
alias_terms = RANGE_SCAN_ALIAS_TERMS.get(domain, set())
original_event_count = len(events)
evidence_ledger = []
mapping_details = []
matched_tags: set[str] = set()
recommended_allowlist_candidates: set[str] = set()
match_counts = {
"exact_id": 0,
"official_tag": 0,
"alias": 0,
"rejected": 0,
}
for index, event in enumerate(events, start=1):
if not isinstance(event, dict):
continue
# VedAstro uses "Name" for event id, and "EventTags" for tags
event_id = event.get("Name") or event.get("id") or event.get("name") or f"event_{index}"
tags = event.get("EventTags") or event.get("tags") or []
if not isinstance(tags, list):
tags = []
tag_set = {str(tag) for tag in tags}
matched_by = "rejected"
matched_terms: list[str] = []
drop_reason = "no_supported_match"
signal_metadata = RANGE_SCAN_SIGNAL_METADATA.get(domain, {}).get(event_id, {})
if event_id in allowed_ids:
matched_by = "exact_id"
matched_terms = [event_id]
drop_reason = ""
else:
official_tag_hits = sorted(tag_set.intersection(official_tags))
if official_tag_hits:
matched_by = "official_tag"
matched_terms = official_tag_hits
drop_reason = ""
else:
haystack_parts = [
str(event_id),
str(event.get("Description") or ""),
str(event.get("description") or ""),
str(event.get("Name") or ""),
" ".join(str(tag) for tag in tags),
]
haystack = " ".join(part.lower() for part in haystack_parts if part)
alias_hits = sorted(term for term in alias_terms if term in haystack)
guard_hits = sorted(term for term in ALIAS_NEGATIVE_GUARD_TERMS if term in haystack)
if alias_hits and not guard_hits:
matched_by = "alias"
matched_terms = alias_hits
drop_reason = ""
match_counts[matched_by] += 1
if matched_by == "rejected":
mapping_details.append(
{
"event_id": event_id,
"matched_by": matched_by,
"matched_terms": matched_terms,
"drop_reason": drop_reason,
"tags": tags,
}
)
continue
if matched_by == "official_tag":
matched_tags.update(matched_terms)
if event_id not in allowed_ids and not tag_set.intersection(allowed_tags):
recommended_allowlist_candidates.add(event_id)
elif matched_by == "alias":
if event_id not in allowed_ids:
recommended_allowlist_candidates.add(event_id)
match_meta = MATCH_METADATA_BY_TYPE[matched_by]
mapping_details.append(
{
"event_id": event_id,
"matched_by": matched_by,
"matched_terms": matched_terms,
"drop_reason": drop_reason,
"tags": tags,
}
)
evidence_ledger.append(
{
"source": "vedastro_service_adapter_candidate",
"operation": "range_scan",
"domain": domain,
"event_id": event_id,
"matched_by": matched_by,
"matched_terms": matched_terms,
"signal_lift": match_meta["signal_lift"],
"confidence": match_meta["confidence"],
"drop_reason": None,
"signal_key": signal_metadata.get("signal_key"),
"signal_label": signal_metadata.get("signal_label") or event.get("name") or event_id,
"signal_family": signal_metadata.get("signal_family"),
"start": event.get("StartTime") or event.get("start") or event.get("start_time") or event.get("start_date"),
"end": event.get("EndTime") or event.get("end") or event.get("end_time") or event.get("end_date"),
"score": event.get("score") if event.get("score") is not None else event.get("strength"),
"tags": tags,
"raw": event,
}
)
top_event = None
if evidence_ledger:
top = max(
evidence_ledger,
key=lambda item: item.get("score") if isinstance(item.get("score"), (int, float)) else float("-inf"),
)
top_event = {
"event_id": top.get("event_id"),
"signal_key": top.get("signal_key"),
"signal_label": top.get("signal_label"),
"signal_family": top.get("signal_family"),
"score": top.get("score"),
"start": top.get("start"),
"end": top.get("end"),
"tags": top.get("tags") or [],
}
metadata = {
**_base_live_metadata(endpoint, request_preview, payload, "range_scan", attempt_count, retry_error_codes),
"vedastro_event_method": request_preview.get("vedastro_event_method"),
"allowlist_domain": domain,
"allowlist_event_count": len(evidence_ledger),
"filtered_event_count": len(evidence_ledger),
"raw_event_count": original_event_count,
"mapping_replay": {
"raw_event_count": original_event_count,
"filtered_event_count": len(evidence_ledger),
"zero_event_domains": [domain] if original_event_count > 0 and not evidence_ledger else [],
"match_counts": match_counts,
"matched_tags": sorted(matched_tags),
"recommended_allowlist_candidates": sorted(recommended_allowlist_candidates),
"events": mapping_details,
},
**(payload.get("source_metadata") or {}),
}
result = {
"backend": "vedastro_service_adapter_candidate",
"available": True,
"status": "ok",
"operation": "range_scan",
"domain": domain,
"request_preview": request_preview,
"event_count": len(evidence_ledger),
"top_event": top_event,
"evidence_ledger": evidence_ledger,
"source_metadata": metadata,
}
result["source_metadata"]["artifact_path"] = _write_artifact(result)
return result
def _source_metadata(endpoint: str) -> dict[str, Any]:
return {
"transport": "http_json_service_boundary",
"endpoint": endpoint,
"provenance_mode": "external_service_candidate",
"timeout_seconds": _timeout_seconds(),
"retry_policy": RETRY_POLICY,
"network_execution_env": ALLOW_NETWORK_ENV,
}
def _post_json(endpoint: str, request_preview: dict[str, Any]) -> dict[str, Any] | str:
official_request_profile = request_preview.get("official_request_profile") if isinstance(request_preview, dict) else None
request_url = endpoint
headers = {"Content-Type": "application/json"}
vedastro_payload = request_preview
if isinstance(official_request_profile, dict):
request_url = f"{endpoint.rstrip('/')}{official_request_profile.get('endpoint_path', '')}"
headers = dict(official_request_profile.get("headers") or headers)
vedastro_payload = dict(official_request_profile.get("body") or {})
req = request.Request(
request_url,
data=json.dumps(vedastro_payload).encode("utf-8"),
headers=headers,
method="POST",
)
with request.urlopen(req, timeout=_timeout_seconds()) as resp:
raw = resp.read().decode("utf-8")
return json.loads(raw)
def _retry_status_codes() -> set[int]:
codes = set()
for value in RETRY_POLICY.get("retry_on", []):
try:
codes.add(int(str(value)))
except ValueError:
continue
return codes
def _post_json_with_retry(endpoint: str, request_preview: dict[str, Any]) -> tuple[dict[str, Any], int, list[int]]:
retry_codes = _retry_status_codes()
retry_error_codes: list[int] = []
max_attempts = int(RETRY_POLICY["max_attempts"])
for attempt in range(1, max_attempts + 1):
try:
payload = _post_json(endpoint, request_preview)
if not isinstance(payload, dict):
return {}, attempt, retry_error_codes
return payload, attempt, retry_error_codes
except error.HTTPError as exc:
if attempt >= max_attempts or exc.code not in retry_codes:
raise
retry_error_codes.append(exc.code)
if _backoff_seconds():
time.sleep(_backoff_seconds())
return {}, max_attempts, retry_error_codes
def run_case(case_id: str) -> dict[str, Any]:
if case_id not in PARITY_CASES:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "unknown_case_id",
"reason": f"Unknown parity case: {case_id}",
}
endpoint = os.environ.get("VEDASTRO_API_ENDPOINT", "").strip()
if not endpoint:
return _unconfigured("VEDASTRO_API_ENDPOINT is not configured; adapter skeleton stops before network access.")
case = PARITY_CASES[case_id]
request_preview = _request_preview(case)
if os.environ.get(ALLOW_NETWORK_ENV, "").strip().lower() not in {"1", "true", "yes"}:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "network_execution_disabled",
"reason": f"{ALLOW_NETWORK_ENV} is not enabled; adapter stops after building request/provenance metadata.",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
try:
payload, attempt_count, retry_error_codes = _post_json_with_retry(endpoint, request_preview)
except error.HTTPError as exc:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "http_error",
"reason": f"VedAstro adapter HTTP error: {exc.code}",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
except error.URLError as exc:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "network_error",
"reason": f"VedAstro adapter network error: {exc.reason}",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
except (TimeoutError, socket.timeout):
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "timeout",
"reason": "VedAstro adapter timed out",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
except json.JSONDecodeError:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "invalid_json",
"reason": "VedAstro adapter received non-JSON response",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
return _normalize_success(payload, endpoint, request_preview)
def _run_range_scan_case(case: dict[str, Any], domain: str, start_date: str, end_date: str) -> dict[str, Any]:
if domain not in SUPPORTED_RANGE_SCAN_DOMAINS:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "unsupported_range_scan_domain",
"reason": f"Unsupported range scan domain: {domain}",
}
request_preview = _range_scan_preview(case, domain, start_date, end_date)
endpoint = os.environ.get("VEDASTRO_API_ENDPOINT", "").strip()
if not endpoint:
result = _unconfigured("VEDASTRO_API_ENDPOINT is not configured; range scan stops before network access.")
result["operation"] = "range_scan"
result["domain"] = domain
result["request_preview"] = request_preview
return result
if os.environ.get(ALLOW_NETWORK_ENV, "").strip().lower() not in {"1", "true", "yes"}:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "network_execution_disabled",
"reason": f"{ALLOW_NETWORK_ENV} is not enabled; range scan stops after building request/provenance metadata.",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
try:
payload, attempt_count, retry_error_codes = _post_json_with_retry(endpoint, request_preview)
except error.HTTPError as exc:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "http_error",
"reason": f"VedAstro range scan HTTP error: {exc.code}",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
except error.URLError as exc:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "network_error",
"reason": f"VedAstro range scan network error: {exc.reason}",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
except (TimeoutError, socket.timeout):
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "timeout",
"reason": "VedAstro range scan timed out",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
except json.JSONDecodeError:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "invalid_json",
"reason": "VedAstro range scan received non-JSON response",
"request_preview": request_preview,
"source_metadata": _source_metadata(endpoint),
}
return _normalize_range_scan_success(payload, endpoint, request_preview, attempt_count, retry_error_codes)
def run_range_scan(case_id: str, domain: str, start_date: str, end_date: str) -> dict[str, Any]:
if case_id not in PARITY_CASES:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "unknown_case_id",
"reason": f"Unknown parity case: {case_id}",
}
return _run_range_scan_case(PARITY_CASES[case_id], domain, start_date, end_date)
def run_range_scan_for_case(
case: dict[str, Any],
domain: str,
start_date: str,
end_date: str,
case_id: str = "user_chart",
) -> dict[str, Any]:
user_case = {
"case_id": case_id,
"year": case.get("year"),
"month": case.get("month"),
"day": case.get("day"),
"hour": case.get("hour"),
"minute": case.get("minute"),
"second": case.get("second", 0),
"lat": case.get("lat"),
"lon": case.get("lon"),
"tz": case.get("tz"),
"ayanamsa_policy": case.get("ayanamsa_policy") or case.get("ayanamsa") or "lahiri",
"node_policy": case.get("node_policy") or case.get("node_mode") or "mean",
}
return _run_range_scan_case(user_case, domain, start_date, end_date)
def run_external_technique(case_id: str, domain: str, method: str, api_endpoint: str) -> dict[str, Any]:
if case_id not in PARITY_CASES:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "unknown_case_id",
"reason": f"Unknown parity case: {case_id}",
}
if domain not in SUPPORTED_EXTERNAL_TECHNIQUE_DOMAINS:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "unsupported_external_technique_domain",
"reason": f"Unsupported external technique domain: {domain}",
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
}
if not method.strip() or not api_endpoint.strip():
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "missing_external_technique_method",
"reason": "Both method and api_endpoint are required for external technique evidence.",
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
}
endpoint = os.environ.get("VEDASTRO_API_ENDPOINT", "").strip()
if not endpoint:
result = _unconfigured(
"VEDASTRO_API_ENDPOINT is not configured; external technique evidence stops before network access."
)
result["operation"] = EXTERNAL_TECHNIQUE_OPERATION
result["role"] = EXTERNAL_TECHNIQUE_ROLE
result["domain"] = domain
result["adjudicator_policy"] = EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY
return result
request_preview = _external_technique_preview(PARITY_CASES[case_id], domain, method, api_endpoint)
if os.environ.get(ALLOW_NETWORK_ENV, "").strip().lower() not in {"1", "true", "yes"}:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "network_execution_disabled",
"reason": (
f"{ALLOW_NETWORK_ENV} is not enabled; external technique evidence stops "
"after building request/provenance metadata."
),
"request_preview": request_preview,
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
"source_metadata": _source_metadata(endpoint),
}
try:
payload = _post_json(endpoint, request_preview)
except error.HTTPError as exc:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "http_error",
"reason": f"VedAstro external technique HTTP error: {exc.code}",
"request_preview": request_preview,
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
"source_metadata": _source_metadata(endpoint),
}
except error.URLError as exc:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "network_error",
"reason": f"VedAstro external technique network error: {exc.reason}",
"request_preview": request_preview,
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
"source_metadata": _source_metadata(endpoint),
}
except (TimeoutError, socket.timeout):
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "timeout",
"reason": "VedAstro external technique timed out",
"request_preview": request_preview,
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
"source_metadata": _source_metadata(endpoint),
}
except json.JSONDecodeError:
return {
"backend": "vedastro_service_adapter_candidate",
"available": False,
"status": "invalid_json",
"reason": "VedAstro external technique received non-JSON response",
"request_preview": request_preview,
"adjudicator_policy": EXTERNAL_TECHNIQUE_ADJUDICATOR_POLICY,
"source_metadata": _source_metadata(endpoint),
}
return _normalize_external_technique_success(payload, endpoint, request_preview)
def main() -> int:
parser = argparse.ArgumentParser(description="VedAstro service adapter skeleton")
parser.add_argument("--print-schema", action="store_true")
parser.add_argument("--case", default="beijing_first_use_demo")
parser.add_argument("--range-scan", action="store_true")
parser.add_argument("--domain", choices=sorted(SUPPORTED_EXTERNAL_TECHNIQUE_DOMAINS), default="marriage")
parser.add_argument("--start-date", default="2026-01-01")
parser.add_argument("--end-date", default="2031-01-01")
parser.add_argument("--external-technique", action="store_true")
parser.add_argument("--method", default="")
parser.add_argument("--api-endpoint", default="")
args = parser.parse_args()
if args.print_schema:
result = schema()
elif args.external_technique:
result = run_external_technique(args.case, args.domain, args.method, args.api_endpoint)
elif args.range_scan:
result = run_range_scan(args.case, args.domain, args.start_date, args.end_date)
else:
result = run_case(args.case)
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())