{"id":4438,"date":"2025-10-31T01:20:20","date_gmt":"2025-10-31T01:20:20","guid":{"rendered":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=4438"},"modified":"2025-10-31T01:24:27","modified_gmt":"2025-10-31T01:24:27","slug":"nl_signal_scythe-sigint-core","status":"publish","type":"post","link":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=4438","title":{"rendered":"NL_SIGNAL_SCYTHE SIGINT CORE"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img data-opt-id=747127841  fetchpriority=\"high\" decoding=\"async\" width=\"1015\" height=\"1017\" src=\"https:\/\/ml6vmqguit1n.i.optimole.com\/w:1015\/h:1017\/f:best\/q:mauto\/id:76ac5a433f499109bba19fe29a5231cd\/http:\/\/172-234-197-23.ip.linodeusercontent.com\/278ecf2db95d8f3f.png\" alt=\"\" class=\"wp-image-4439\"\/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>import logging\nimport numpy as np\nimport threading\nimport time\nimport json\nimport requests\nimport os\nfrom math import isclose\nfrom queue import Queue\nfrom dataclasses import dataclass, field\nfrom typing import Dict, List, Any, Optional, Tuple\n\nlogger = logging.getLogger(\"SignalIntelligence\")\n\n# Custom JSON encoder for numpy types\nclass NumpyJSONEncoder(json.JSONEncoder):\n    \"\"\"JSON encoder that properly handles numpy types\"\"\"\n    def default(self, obj):\n        if isinstance(obj, np.integer):\n            return int(obj)\n        elif isinstance(obj, np.floating):\n            return float(obj)\n        elif isinstance(obj, np.ndarray):\n            return obj.tolist()\n        return super().default(obj)\n\n@dataclass\nclass GeoPosition:\n    \"\"\"Geographic position data\"\"\"\n    latitude: float\n    longitude: float\n    altitude: Optional&#91;float] = None\n    accuracy: Optional&#91;float] = None\n    timestamp: float = field(default_factory=time.time)\n    \n    def to_dict(self) -&gt; Dict&#91;str, Any]:\n        \"\"\"Convert to dictionary for serialization\"\"\"\n        return {\n            \"lat\": float(self.latitude),\n            \"lon\": float(self.longitude),\n            \"alt\": float(self.altitude) if self.altitude is not None else None,\n            \"accuracy\": float(self.accuracy) if self.accuracy is not None else None,\n            \"timestamp\": float(self.timestamp)\n        }\n\n@dataclass\nclass RFSignal:\n    \"\"\"RF Signal data structure\"\"\"\n    id: str\n    timestamp: float\n    frequency: float\n    bandwidth: float\n    power: float\n    iq_data: np.ndarray\n    source: str\n    classification: Optional&#91;str] = None\n    confidence: float = 0.0\n    metadata: Dict&#91;str, Any] = field(default_factory=dict)\n    geo_position: Optional&#91;GeoPosition] = None\n    \n    def to_dict(self) -&gt; Dict&#91;str, Any]:\n        \"\"\"Convert to dictionary for JSON serialization (excludes IQ data)\"\"\"\n        result = {\n            \"id\": self.id,\n            \"timestamp\": float(self.timestamp),\n            \"frequency\": float(self.frequency),\n            \"frequency_mhz\": float(self.frequency \/ 1e6),\n            \"bandwidth\": float(self.bandwidth),\n            \"bandwidth_khz\": float(self.bandwidth \/ 1e3),\n            \"power\": float(self.power),\n            \"source\": self.source,\n            \"classification\": self.classification,\n            \"confidence\": float(self.confidence),\n            \"metadata\": self.metadata\n        }\n        \n        # Add geo position if available\n        if self.geo_position:\n            result&#91;\"geo_position\"] = self.geo_position.to_dict()\n            \n        return result\n\nclass SignalIntelligenceSystem:\n    \"\"\"Main Signal Intelligence System class\"\"\"\n    def __init__(self, config, comm_network):\n        self.config = config\n        self.comm_network = comm_network\n        self.running = False\n        self.signal_queue = Queue()\n        self.processed_signals = &#91;]\n        self.noise_floor = -120  # Default noise floor in dBm\n        self.geo_visualization_url = None\n        self.geo_areas_of_operation = &#91;]\n        \n        # ATL\/TWPA design-aware processing\n        self.atl_design = None\n        self.recent_freqs_hz = &#91;]  # small FIFO for mixing checks\n        \n        # Initialize core components\n        self._initialize_components()\n        \n        # Setup geo visualization integration if available\n        self._setup_geo_integration()\n        \n        # Load ATL\/TWPA design configuration\n        self._load_atl_design()\n        \n    def _initialize_components(self):\n        \"\"\"Initialize signal intelligence components\"\"\"\n        # This will be implemented in subclasses or extended\n        pass\n    \n    def _setup_geo_integration(self):\n        \"\"\"Setup integration with geographic visualization\"\"\"\n        try:\n            # Check for geo visualization configuration\n            geo_config_path = \"config\/geo_visualization.json\"\n            if os.path.exists(geo_config_path):\n                with open(geo_config_path, \"r\") as f:\n                    geo_config = json.load(f)\n                    \n                # Extract server information\n                server_config = geo_config.get(\"server\", {})\n                host = server_config.get(\"host\", \"localhost\")\n                port = server_config.get(\"port\", 5050)\n                \n                # Set geo visualization URL\n                self.geo_visualization_url = f\"http:\/\/{host}:{port}\/api\/signals\"\n                \n                # Load areas of operation\n                ao_config = geo_config.get(\"areas_of_operation\", {})\n                ao_presets = ao_config.get(\"presets\", &#91;])\n                \n                if ao_presets:\n                    self.geo_areas_of_operation = ao_presets\n                    logger.info(f\"Loaded {len(ao_presets)} Areas of Operation for geo visualization\")\n                \n                logger.info(f\"Geographic visualization integration configured at {self.geo_visualization_url}\")\n        except Exception as e:\n            logger.warning(f\"Failed to configure geo visualization integration: {e}\")\n    \n    def _load_atl_design(self):\n        \"\"\"Load optional ATL\/TWPA design facts from Arxiv 2510.24753v1.\"\"\"\n        try:\n            path = \"config\/atl_design.json\"\n            if os.path.exists(path):\n                with open(path, \"r\") as f:\n                    d = json.load(f)\n                # normalize expected keys\n                self.atl_design = {\n                    \"pump_hz\": float(d.get(\"pump_hz\")) if d.get(\"pump_hz\") else None,\n                    \"rpm_notch_hz\": float(d.get(\"rpm_notch_hz\")) if d.get(\"rpm_notch_hz\") else None,\n                    \"rpm_pole_hz\": float(d.get(\"rpm_pole_hz\")) if d.get(\"rpm_pole_hz\") else None,\n                    \"stopbands\": &#91;\n                        {\"center_hz\": float(sb&#91;\"center_hz\"]), \"width_hz\": float(sb&#91;\"width_hz\"])}\n                        for sb in d.get(\"stopbands\", &#91;])\n                    ],\n                    \"mixing_mode\": d.get(\"mixing_mode\", \"4WM\")  # or \"3WM\"\n                }\n                logger.info(\"ATL\/TWPA design loaded from config\/atl_design.json\")\n        except Exception as e:\n            logger.warning(f\"Failed to load ATL design: {e}\")\n\n    def _label_atl_band(self, f_hz: float, tol_hz: float = 0.01e9):\n        \"\"\"Return band label based on design facts from ATL synthesis.\"\"\"\n        if not self.atl_design:\n            return \"unknown\", {}\n        d = self.atl_design\n        info = {}\n\n        # near rpm notch \/ pole (phase-matching feature for 4WM)\n        if d.get(\"rpm_notch_hz\") and abs(f_hz - d&#91;\"rpm_notch_hz\"]) &lt;= tol_hz:\n            info&#91;\"near_rpm_notch\"] = True\n            return \"near_notch\", info\n        if d.get(\"rpm_pole_hz\") and abs(f_hz - d&#91;\"rpm_pole_hz\"]) &lt;= tol_hz:\n            info&#91;\"near_rpm_pole\"] = True\n            # still a passband point, but important\n            return \"passband\", info\n\n        # stopbands (e.g., wide 3fp gap)\n        for sb in d.get(\"stopbands\", &#91;]):\n            if abs(f_hz - sb&#91;\"center_hz\"]) &lt;= sb&#91;\"width_hz\"] \/ 2:\n                info&#91;\"stopband_center_hz\"] = sb&#91;\"center_hz\"]\n                return \"stopband\", info\n\n        return \"passband\", info\n\n    def _mixing_relations(self, f_hz: float, ppm: float = 150.0):\n        \"\"\"Compute candidate mixing partners to annotate metadata.\"\"\"\n        if not self.atl_design or not self.atl_design.get(\"pump_hz\"):\n            return {}\n        fp = self.atl_design&#91;\"pump_hz\"]\n        tol = fp * ppm * 1e-6  # parts-per-million window\n\n        rel = {\"near_3fp\": False, \"idlers\": &#91;]}\n\n        # third harmonic guard (esp. for KTWPA)\n        if abs(f_hz - 3.0 * fp) &lt;= tol:\n            rel&#91;\"near_3fp\"] = True\n\n        # 4WM: idlers around (2fp - fs) and (2fp + fs)\n        if self.atl_design.get(\"mixing_mode\", \"4WM\").upper() == \"4WM\":\n            for fs in self.recent_freqs_hz&#91;-64:]:\n                i1 = abs((2.0 * fp) - fs)  # 2fp - fs\n                i2 = abs((2.0 * fp) + fs)  # rarely in band, still annotate\n                if abs(f_hz - i1) &lt;= tol:\n                    rel&#91;\"idlers\"].append({\"mode\": \"4WM\", \"expr\": \"2fp - fs\", \"fs_hz\": fs, \"idler_hz\": i1})\n                if abs(f_hz - i2) &lt;= tol:\n                    rel&#91;\"idlers\"].append({\"mode\": \"4WM\", \"expr\": \"2fp + fs\", \"fs_hz\": fs, \"idler_hz\": i2})\n\n        # 3WM: idlers around (fp - fs) and (fp + fs)\n        else:\n            for fs in self.recent_freqs_hz&#91;-64:]:\n                i1 = abs(fp - fs)  # usual forward idler\n                i2 = abs(fp + fs)\n                if abs(f_hz - i1) &lt;= tol:\n                    rel&#91;\"idlers\"].append({\"mode\": \"3WM\", \"expr\": \"fp - fs\", \"fs_hz\": fs, \"idler_hz\": i1})\n                if abs(f_hz - i2) &lt;= tol:\n                    rel&#91;\"idlers\"].append({\"mode\": \"3WM\", \"expr\": \"fp + fs\", \"fs_hz\": fs, \"idler_hz\": i2})\n\n        return rel\n\n    def annotate_signal_with_atl(self, signal: \"RFSignal\"):\n        \"\"\"Attach ATL\/TWPA labels to signal.metadata (no-op if no design).\"\"\"\n        try:\n            # keep a small memory for mixing checks\n            self.recent_freqs_hz.append(float(signal.frequency))\n            if len(self.recent_freqs_hz) &gt; 512:\n                self.recent_freqs_hz = self.recent_freqs_hz&#91;-256:]\n\n            band_label, band_info = self._label_atl_band(signal.frequency)\n            mix_info = self._mixing_relations(signal.frequency)\n\n            signal.metadata.setdefault(\"atl\", {})\n            signal.metadata&#91;\"atl\"].update({\n                \"band_label\": band_label,\n                **band_info,\n                **mix_info\n            })\n            \n            # Log important ATL events\n            if band_info.get(\"near_rpm_notch\") or mix_info.get(\"near_3fp\") or band_label == \"stopband\":\n                logger.info(f\"ATL event detected - Signal {signal.id}: {band_label}, near_3fp: {mix_info.get('near_3fp', False)}\")\n            \n        except Exception as e:\n            logger.debug(f\"ATL annotate failed: {e}\")\n    \n    def process_atl_alerts(self, signal: \"RFSignal\"):\n        \"\"\"Process ATL-related alerts and update classifications as needed.\"\"\"\n        if not self.atl_design or \"atl\" not in signal.metadata:\n            return\n            \n        atl_data = signal.metadata&#91;\"atl\"]\n        \n        # Check for important ATL events that warrant classification updates\n        alert_conditions = &#91;]\n        \n        if atl_data.get(\"near_3fp\"):\n            alert_conditions.append(\"near_3fp_harmonic\")\n            \n        if atl_data.get(\"band_label\") == \"stopband\":\n            alert_conditions.append(\"in_designed_stopband\")\n            \n        if atl_data.get(\"near_rpm_notch\"):\n            alert_conditions.append(\"near_phase_matching_notch\")\n            \n        if atl_data.get(\"idlers\"):\n            alert_conditions.append(f\"parametric_mixing_detected({len(atl_data&#91;'idlers'])})\")\n        \n        # Update classification if any alert conditions are met\n        if alert_conditions:\n            new_classification = f\"ATL_Event: {', '.join(alert_conditions)}\"\n            self.update_signal_classification(\n                signal.id, \n                new_classification, \n                0.85,  # High confidence for design-based detection\n                update_info={\"atl\": atl_data, \"alert_conditions\": alert_conditions}\n            )\n            \n    def send_signal_to_geo_visualization(self, signal: RFSignal) -&gt; bool:\n        \"\"\"Send signal to geographic visualization system if available\"\"\"\n        if not self.geo_visualization_url:\n            return False\n            \n        # Skip signals without geo position\n        if not signal.geo_position:\n            return False\n            \n        try:\n            # Convert signal to dict\n            signal_dict = signal.to_dict()\n            \n            # Send to geo visualization\n            response = requests.post(\n                f\"{self.geo_visualization_url}\/add\",\n                json=signal_dict,\n                headers={\"Content-Type\": \"application\/json\"},\n                timeout=1\n            )\n            \n            if response.status_code == 200:\n                logger.debug(f\"Sent signal {signal.id} to geo visualization\")\n                return True\n            else:\n                logger.warning(f\"Failed to send signal to geo visualization: {response.status_code}\")\n                return False\n                \n        except Exception as e:\n            logger.warning(f\"Error sending signal to geo visualization: {e}\")\n            return False\n    \n    def get_signals(self, start_time=None, end_time=None, min_frequency=None, max_frequency=None, signal_id=None):\n        \"\"\"\n        Get all processed signals with optional filtering\n        \n        Args:\n            start_time (float, optional): Filter signals after this timestamp\n            end_time (float, optional): Filter signals before this timestamp\n            min_frequency (float, optional): Filter signals above this frequency (Hz)\n            max_frequency (float, optional): Filter signals below this frequency (Hz)\n            signal_id (str, optional): Get a specific signal by ID\n            \n        Returns:\n            list: List of signal dictionaries\n        \"\"\"\n        # Apply filters\n        filtered_signals = self.processed_signals.copy()\n        \n        # Filter by ID if provided\n        if signal_id:\n            filtered_signals = &#91;s for s in filtered_signals if s.id == signal_id]\n            \n        # Apply time filters\n        if start_time is not None:\n            filtered_signals = &#91;s for s in filtered_signals if s.timestamp &gt;= start_time]\n            \n        if end_time is not None:\n            filtered_signals = &#91;s for s in filtered_signals if s.timestamp &lt;= end_time]\n            \n        # Apply frequency filters\n        if min_frequency is not None:\n            filtered_signals = &#91;s for s in filtered_signals if s.frequency &gt;= min_frequency]\n            \n        if max_frequency is not None:\n            filtered_signals = &#91;s for s in filtered_signals if s.frequency &lt;= max_frequency]\n        \n        # Convert RFSignal objects to dictionaries for JSON serialization\n        signals_list = &#91;]\n        for signal in filtered_signals:\n            signals_list.append(signal.to_dict())\n            \n        return signals_list\n        \n    def update_signal_classification(self, signal_id, classification, confidence, update_info=None):\n        \"\"\"\n        Update the classification of a signal\n        \n        Args:\n            signal_id (str): ID of the signal to update\n            classification (str): New classification\n            confidence (float): New confidence value\n            update_info (dict, optional): Additional information about the update\n            \n        Returns:\n            bool: True if the signal was updated, False otherwise\n        \"\"\"\n        # Find the signal\n        for signal in self.processed_signals:\n            if signal.id == signal_id:\n                # Store old values\n                old_classification = signal.classification\n                old_confidence = signal.confidence\n                \n                # Update classification\n                signal.classification = classification\n                signal.confidence = confidence\n                \n                # Add to classification history\n                if \"classification_history\" not in signal.metadata:\n                    signal.metadata&#91;\"classification_history\"] = &#91;]\n                    \n                history_entry = {\n                    \"timestamp\": time.time(),\n                    \"old_classification\": old_classification,\n                    \"new_classification\": classification,\n                    \"old_confidence\": old_confidence,\n                    \"new_confidence\": confidence\n                }\n                \n                # Add update info if provided\n                if update_info:\n                    history_entry.update(update_info)\n                    \n                signal.metadata&#91;\"classification_history\"].append(history_entry)\n                \n                # Re-annotate with ATL if this is an ATL-related update\n                if update_info and \"atl\" in update_info:\n                    self.annotate_signal_with_atl(signal)\n                \n                # Log the update\n                logger.info(f\"Updated signal {signal_id} classification: {old_classification} \u2192 {classification} (confidence: {confidence:.2f})\")\n                \n                # Publish update to communication network if available\n                if hasattr(self, \"comm_network\") and self.comm_network:\n                    self.comm_network.publish(\n                        \"signal_classification_updated\",\n                        {\n                            \"signal_id\": signal_id,\n                            \"old_classification\": old_classification,\n                            \"new_classification\": classification,\n                            \"old_confidence\": old_confidence,\n                            \"new_confidence\": confidence,\n                            \"update_info\": update_info\n                        },\n                        sender=\"signal_intelligence\"\n                    )\n                \n                return True\n                \n        # Signal not found\n        logger.warning(f\"Cannot update classification: Signal {signal_id} not found\")\n        return False\n    \n    def get_rf_environment(self):\n        \"\"\"\n        Get the current RF environment information\n        \n        Returns:\n            dict: RF environment data\n        \"\"\"\n        # Get current time\n        current_time = time.time()\n        \n        # Find active signals (last 60 seconds)\n        active_signals = &#91;s for s in self.processed_signals if current_time - s.timestamp &lt; 60]\n        \n        # Group signals by frequency bands\n        frequency_bands = {}\n        for signal in active_signals:\n            # Convert to MHz for readability\n            freq_mhz = signal.frequency \/ 1_000_000\n            band_key = int(freq_mhz \/ 100) * 100  # Group by 100 MHz bands\n            \n            if band_key not in frequency_bands:\n                frequency_bands&#91;band_key] = {\n                    \"center_frequency_mhz\": band_key + 50,  # Center of the band\n                    \"bandwidth_mhz\": 100,\n                    \"signals\": &#91;],\n                    \"power_values\": &#91;],\n                    \"classifications\": set()\n                }\n                \n            frequency_bands&#91;band_key]&#91;\"signals\"].append(signal)\n            frequency_bands&#91;band_key]&#91;\"power_values\"].append(signal.power)\n            \n            if signal.classification and signal.classification != \"Unknown\":\n                frequency_bands&#91;band_key]&#91;\"classifications\"].add(signal.classification)\n        \n        # Format frequency bands for output\n        formatted_bands = &#91;]\n        for band_key, band_data in frequency_bands.items():\n            band_dict = {\n                \"center_frequency_mhz\": band_data&#91;\"center_frequency_mhz\"],\n                \"bandwidth_mhz\": band_data&#91;\"bandwidth_mhz\"],\n                \"power_dbm\": np.mean(band_data&#91;\"power_values\"]) if band_data&#91;\"power_values\"] else -120,\n                \"signal_count\": len(band_data&#91;\"signals\"]),\n                \"protocols_detected\": list(band_data&#91;\"classifications\"])\n            }\n            \n            # Add ATL band labeling if design is present\n            if self.atl_design:\n                center = band_data&#91;\"center_frequency_mhz\"] * 1e6\n                label, _ = self._label_atl_band(center, tol_hz=0.02e9)\n                band_dict&#91;\"atl_band_label\"] = label\n            \n            formatted_bands.append(band_dict)\n            \n        # Calculate spectrum statistics\n        spectrum_data = {\n            \"timestamp\": current_time,\n            \"min_frequency_mhz\": min(&#91;b&#91;\"center_frequency_mhz\"] - b&#91;\"bandwidth_mhz\"]\/2 for b in formatted_bands]) if formatted_bands else 0,\n            \"max_frequency_mhz\": max(&#91;b&#91;\"center_frequency_mhz\"] + b&#91;\"bandwidth_mhz\"]\/2 for b in formatted_bands]) if formatted_bands else 0,\n            \"avg_noise_floor_dbm\": self.noise_floor,\n            \"frequency_bands\": formatted_bands\n        }\n        \n        return spectrum_data\n    \n    def start(self):\n        \"\"\"Start Signal Intelligence System\"\"\"\n        logger.info(\"Starting Signal Intelligence System\")\n        self.running = True\n        \n        # Start signal processing thread\n        processing_thread = threading.Thread(target=self._signal_processing_loop)\n        processing_thread.daemon = True\n        processing_thread.start()\n    \n    def _signal_processing_loop(self):\n        \"\"\"Main signal processing loop\"\"\"\n        while self.running:\n            try:\n                # Process signals from queue\n                if not self.signal_queue.empty():\n                    signal_data = self.signal_queue.get(timeout=1)\n                    self.process_signal(signal_data)\n                    self.signal_queue.task_done()\n                else:\n                    time.sleep(0.1)\n            except Exception as e:\n                logger.error(f\"Error in signal processing: {e}\")\n                time.sleep(1)\n    \n    def process_signal(self, signal_data):\n        \"\"\"Process incoming signal data - to be implemented by subclasses\"\"\"\n        raise NotImplementedError(\"Subclasses must implement process_signal method\")\n    \n    def shutdown(self):\n        \"\"\"Shutdown the system\"\"\"\n        logger.info(\"Shutting down Signal Intelligence System\")\n        self.running = False\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":4439,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[13,10],"tags":[18,16,15],"class_list":["post-4438","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-culture-shock","category-signal_scythe","tag-economicwarfare","tag-16","tag-15"],"_links":{"self":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/4438","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4438"}],"version-history":[{"count":2,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/4438\/revisions"}],"predecessor-version":[{"id":4441,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/4438\/revisions\/4441"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/media\/4439"}],"wp:attachment":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4438"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4438"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4438"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}