diff --git a/docs/curl_metric_sample.md b/docs/curl_metric_sample.md
new file mode 100644
index 0000000..bc4aad1
--- /dev/null
+++ b/docs/curl_metric_sample.md
@@ -0,0 +1,208 @@
+ curl -s http://127.0.0.1:19100/metrics
+# HELP netconf_scrape_duration_seconds Duration of last NETCONF scrape per device
+# TYPE netconf_scrape_duration_seconds gauge
+netconf_scrape_duration_seconds{device="h3c-live-1"} 17.09970760345459
+# HELP netconf_scrape_success Whether last NETCONF scrape succeeded (1) or failed (0)
+# TYPE netconf_scrape_success gauge
+netconf_scrape_success{device="h3c-live-1"} 1.0
+# HELP netconf_last_scrape_timestamp_seconds Timestamp of last NETCONF scrape per device
+# TYPE netconf_last_scrape_timestamp_seconds gauge
+netconf_last_scrape_timestamp_seconds{device="h3c-live-1"} 1.7643102845284216e+09
+# HELP transceiver_data_staleness_seconds Age of last successful transceiver scrape per device
+# TYPE transceiver_data_staleness_seconds gauge
+# HELP netconf_scrape_errors_total Total number of NETCONF scrape errors by device and type
+# TYPE netconf_scrape_errors_total counter
+# HELP transceiver_temperature_celsius Transceiver temperature in degrees Celsius
+# TYPE transceiver_temperature_celsius gauge
+transceiver_temperature_celsius{component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 41.0
+transceiver_temperature_celsius{component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 41.0
+transceiver_temperature_celsius{component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/2"} 42.0
+transceiver_temperature_celsius{component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/2"} 42.0
+transceiver_temperature_celsius{component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/3"} 42.0
+transceiver_temperature_celsius{component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/3"} 42.0
+transceiver_temperature_celsius{component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/4"} 41.0
+transceiver_temperature_celsius{component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/4"} 41.0
+transceiver_temperature_celsius{component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 41.0
+transceiver_temperature_celsius{component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/66"} 41.0
+transceiver_temperature_celsius{component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 42.0
+transceiver_temperature_celsius{component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 41.0
+# HELP transceiver_supply_voltage_volts Transceiver supply voltage
+# TYPE transceiver_supply_voltage_volts gauge
+transceiver_supply_voltage_volts{component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 3.34
+transceiver_supply_voltage_volts{component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 3.34
+transceiver_supply_voltage_volts{component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/2"} 3.34
+transceiver_supply_voltage_volts{component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/2"} 3.34
+transceiver_supply_voltage_volts{component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/3"} 3.28
+transceiver_supply_voltage_volts{component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/3"} 3.28
+transceiver_supply_voltage_volts{component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/4"} 3.3
+transceiver_supply_voltage_volts{component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/4"} 3.3
+transceiver_supply_voltage_volts{component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 3.33
+transceiver_supply_voltage_volts{component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/66"} 3.32
+transceiver_supply_voltage_volts{component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 3.3
+transceiver_supply_voltage_volts{component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 3.3
+# HELP transceiver_present Transceiver present state (1 present, 0 otherwise)
+# TYPE transceiver_present gauge
+transceiver_present{component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_present{component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_present{component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_present{component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_present{component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/3"} 1.0
+transceiver_present{component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/3"} 1.0
+transceiver_present{component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/4"} 1.0
+transceiver_present{component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/4"} 1.0
+transceiver_present{component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 1.0
+transceiver_present{component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/66"} 1.0
+transceiver_present{component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 1.0
+transceiver_present{component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 1.0
+# HELP transceiver_channel_rx_power_dbm Receive optical power per channel (dBm)
+# TYPE transceiver_channel_rx_power_dbm gauge
+transceiver_channel_rx_power_dbm{channel="1/0/1:1",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} -3.57
+transceiver_channel_rx_power_dbm{channel="1/0/1:2",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 1.07
+transceiver_channel_rx_power_dbm{channel="1/0/1:1",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 1.06
+transceiver_channel_rx_power_dbm{channel="1/0/1:2",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 1.07
+transceiver_channel_rx_power_dbm{channel="1/0/2:1",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/2"} 1.53
+transceiver_channel_rx_power_dbm{channel="1/0/1:2",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/1"} 1.07
+transceiver_channel_rx_power_dbm{channel="1/0/2:1",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/2"} 1.94
+transceiver_channel_rx_power_dbm{channel="1/0/1:2",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/1"} 1.85
+transceiver_channel_rx_power_dbm{channel="1/0/3:1",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/3"} 1.74
+transceiver_channel_rx_power_dbm{channel="1/0/2:2",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/2"} 1.85
+transceiver_channel_rx_power_dbm{channel="1/0/3:1",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/3"} 1.94
+transceiver_channel_rx_power_dbm{channel="1/0/2:2",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/2"} 1.85
+transceiver_channel_rx_power_dbm{channel="1/0/4:1",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/4"} 1.23
+transceiver_channel_rx_power_dbm{channel="1/0/2:2",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/2"} 1.85
+transceiver_channel_rx_power_dbm{channel="1/0/4:1",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/4"} 1.76
+transceiver_channel_rx_power_dbm{channel="1/0/2:2",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/2"} 1.63
+transceiver_channel_rx_power_dbm{channel="1/0/65:1",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 0.59
+transceiver_channel_rx_power_dbm{channel="1/0/4:2",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/4"} 1.63
+transceiver_channel_rx_power_dbm{channel="1/0/65:3",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 0.72
+transceiver_channel_rx_power_dbm{channel="1/0/65:4",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 0.63
+transceiver_channel_rx_power_dbm{channel="1/0/66:1",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/66"} 0.48
+transceiver_channel_rx_power_dbm{channel="1/0/4:2",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/4"} 0.67
+transceiver_channel_rx_power_dbm{channel="1/0/65:3",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 0.72
+transceiver_channel_rx_power_dbm{channel="1/0/65:4",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 0.66
+transceiver_channel_rx_power_dbm{channel="1/0/67:1",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 0.53
+transceiver_channel_rx_power_dbm{channel="1/0/67:2",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 0.67
+transceiver_channel_rx_power_dbm{channel="1/0/65:3",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/65"} 1.09
+transceiver_channel_rx_power_dbm{channel="1/0/67:4",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 0.66
+transceiver_channel_rx_power_dbm{channel="1/0/68:1",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 1.43
+transceiver_channel_rx_power_dbm{channel="1/0/67:2",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 1.18
+transceiver_channel_rx_power_dbm{channel="1/0/68:3",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 1.09
+transceiver_channel_rx_power_dbm{channel="1/0/67:4",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 0.92
+# HELP transceiver_channel_tx_power_dbm Transmit optical power per channel (dBm)
+# TYPE transceiver_channel_tx_power_dbm gauge
+transceiver_channel_tx_power_dbm{channel="1/0/1:1",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 2.03
+transceiver_channel_tx_power_dbm{channel="1/0/1:2",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 2.11
+transceiver_channel_tx_power_dbm{channel="1/0/1:1",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 2.05
+transceiver_channel_tx_power_dbm{channel="1/0/1:2",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 2.11
+transceiver_channel_tx_power_dbm{channel="1/0/2:1",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/2"} 1.41
+transceiver_channel_tx_power_dbm{channel="1/0/1:2",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/1"} 2.11
+transceiver_channel_tx_power_dbm{channel="1/0/2:1",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/2"} 1.66
+transceiver_channel_tx_power_dbm{channel="1/0/1:2",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/1"} 1.74
+transceiver_channel_tx_power_dbm{channel="1/0/3:1",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/3"} 1.9
+transceiver_channel_tx_power_dbm{channel="1/0/2:2",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/2"} 1.74
+transceiver_channel_tx_power_dbm{channel="1/0/3:1",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/3"} 2.15
+transceiver_channel_tx_power_dbm{channel="1/0/2:2",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/2"} 1.74
+transceiver_channel_tx_power_dbm{channel="1/0/4:1",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/4"} 1.88
+transceiver_channel_tx_power_dbm{channel="1/0/2:2",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/2"} 1.74
+transceiver_channel_tx_power_dbm{channel="1/0/4:1",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/4"} 1.91
+transceiver_channel_tx_power_dbm{channel="1/0/2:2",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/2"} 1.82
+transceiver_channel_tx_power_dbm{channel="1/0/65:1",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 1.75
+transceiver_channel_tx_power_dbm{channel="1/0/4:2",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/4"} 1.82
+transceiver_channel_tx_power_dbm{channel="1/0/65:3",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 1.98
+transceiver_channel_tx_power_dbm{channel="1/0/65:4",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 1.94
+transceiver_channel_tx_power_dbm{channel="1/0/66:1",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/66"} 2.17
+transceiver_channel_tx_power_dbm{channel="1/0/4:2",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/4"} 2.09
+transceiver_channel_tx_power_dbm{channel="1/0/65:3",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 1.98
+transceiver_channel_tx_power_dbm{channel="1/0/65:4",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 2.03
+transceiver_channel_tx_power_dbm{channel="1/0/67:1",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 1.75
+transceiver_channel_tx_power_dbm{channel="1/0/67:2",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 2.09
+transceiver_channel_tx_power_dbm{channel="1/0/65:3",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/65"} 1.88
+transceiver_channel_tx_power_dbm{channel="1/0/67:4",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 2.03
+transceiver_channel_tx_power_dbm{channel="1/0/68:1",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 2.05
+transceiver_channel_tx_power_dbm{channel="1/0/67:2",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 2.23
+transceiver_channel_tx_power_dbm{channel="1/0/68:3",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 1.88
+transceiver_channel_tx_power_dbm{channel="1/0/67:4",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 2.09
+# HELP transceiver_channel_bias_current_ma Laser bias current per channel (mA)
+# TYPE transceiver_channel_bias_current_ma gauge
+transceiver_channel_bias_current_ma{channel="1/0/1:1",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 8.72
+transceiver_channel_bias_current_ma{channel="1/0/1:2",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 8.63
+transceiver_channel_bias_current_ma{channel="1/0/1:1",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 8.65
+transceiver_channel_bias_current_ma{channel="1/0/1:2",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 8.63
+transceiver_channel_bias_current_ma{channel="1/0/2:1",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/2"} 8.91
+transceiver_channel_bias_current_ma{channel="1/0/1:2",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/1"} 8.63
+transceiver_channel_bias_current_ma{channel="1/0/2:1",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/2"} 8.68
+transceiver_channel_bias_current_ma{channel="1/0/1:2",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/1"} 8.82
+transceiver_channel_bias_current_ma{channel="1/0/3:1",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/3"} 8.65
+transceiver_channel_bias_current_ma{channel="1/0/2:2",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/2"} 8.82
+transceiver_channel_bias_current_ma{channel="1/0/3:1",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/3"} 8.51
+transceiver_channel_bias_current_ma{channel="1/0/2:2",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/2"} 8.82
+transceiver_channel_bias_current_ma{channel="1/0/4:1",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/4"} 8.58
+transceiver_channel_bias_current_ma{channel="1/0/2:2",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/2"} 8.82
+transceiver_channel_bias_current_ma{channel="1/0/4:1",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/4"} 8.89
+transceiver_channel_bias_current_ma{channel="1/0/2:2",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/2"} 8.65
+transceiver_channel_bias_current_ma{channel="1/0/65:1",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 8.84
+transceiver_channel_bias_current_ma{channel="1/0/4:2",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/4"} 8.65
+transceiver_channel_bias_current_ma{channel="1/0/65:3",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 8.84
+transceiver_channel_bias_current_ma{channel="1/0/65:4",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 8.63
+transceiver_channel_bias_current_ma{channel="1/0/66:1",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/66"} 8.8
+transceiver_channel_bias_current_ma{channel="1/0/4:2",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/4"} 8.67
+transceiver_channel_bias_current_ma{channel="1/0/65:3",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 8.84
+transceiver_channel_bias_current_ma{channel="1/0/65:4",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 8.65
+transceiver_channel_bias_current_ma{channel="1/0/67:1",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 8.77
+transceiver_channel_bias_current_ma{channel="1/0/67:2",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 8.67
+transceiver_channel_bias_current_ma{channel="1/0/65:3",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/65"} 8.7
+transceiver_channel_bias_current_ma{channel="1/0/67:4",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 8.65
+transceiver_channel_bias_current_ma{channel="1/0/68:1",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 8.89
+transceiver_channel_bias_current_ma{channel="1/0/67:2",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 8.63
+transceiver_channel_bias_current_ma{channel="1/0/68:3",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 8.7
+transceiver_channel_bias_current_ma{channel="1/0/67:4",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 8.91
+# HELP transceiver_channel_laser_temperature_celsius Laser temperature per channel (Celsius)
+# TYPE transceiver_channel_laser_temperature_celsius gauge
+# HELP transceiver_info_info Transceiver static information
+# TYPE transceiver_info_info gauge
+transceiver_info_info{component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/1",serial="G80231AM995701HH",vendor="H3C"} 1.0
+transceiver_info_info{component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/1",serial="G80231AM995701HH",vendor="H3C"} 1.0
+transceiver_info_info{component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/2",serial="G80231AM995701ML",vendor="H3C"} 1.0
+transceiver_info_info{component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/2",serial="G80231AM995701ML",vendor="H3C"} 1.0
+transceiver_info_info{component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/3",serial="G80231AM995701MQ",vendor="H3C"} 1.0
+transceiver_info_info{component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/3",serial="G80231AM995701MQ",vendor="H3C"} 1.0
+transceiver_info_info{component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/4",serial="G80231AM995701JW",vendor="H3C"} 1.0
+transceiver_info_info{component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/4",serial="G80231AM995701JW",vendor="H3C"} 1.0
+transceiver_info_info{component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/65",serial="G80231AM995701MK",vendor="H3C"} 1.0
+transceiver_info_info{component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/66",serial="G80231AM995701JX",vendor="H3C"} 1.0
+transceiver_info_info{component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/67",serial="G80231AM995701JQ",vendor="H3C"} 1.0
+transceiver_info_info{component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",form_factor="",hardware_rev="",part_number="EQ854HG01M3-H3C",port="1/0/68",serial="G80231AM995701SK",vendor="H3C"} 1.0
+# HELP transceiver_channel_info_info Transceiver channel info
+# TYPE transceiver_channel_info_info gauge
+transceiver_channel_info_info{channel="1/0/1:1",channel_index="1",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_channel_info_info{channel="1/0/1:2",channel_index="2",component_name="63.TwoHundredGigE1/0/1:1",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_channel_info_info{channel="1/0/1:1",channel_index="1",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_channel_info_info{channel="1/0/1:2",channel_index="2",component_name="64.TwoHundredGigE1/0/1:2",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_channel_info_info{channel="1/0/2:1",channel_index="1",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_channel_info_info{channel="1/0/1:2",channel_index="2",component_name="67.TwoHundredGigE1/0/2:1",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_channel_info_info{channel="1/0/2:1",channel_index="1",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_channel_info_info{channel="1/0/1:2",channel_index="2",component_name="68.TwoHundredGigE1/0/2:2",device="h3c-live-1",port="1/0/1"} 1.0
+transceiver_channel_info_info{channel="1/0/3:1",channel_index="1",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/3"} 1.0
+transceiver_channel_info_info{channel="1/0/2:2",channel_index="2",component_name="71.TwoHundredGigE1/0/3:1",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_channel_info_info{channel="1/0/3:1",channel_index="1",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/3"} 1.0
+transceiver_channel_info_info{channel="1/0/2:2",channel_index="2",component_name="72.TwoHundredGigE1/0/3:2",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_channel_info_info{channel="1/0/4:1",channel_index="1",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/4"} 1.0
+transceiver_channel_info_info{channel="1/0/2:2",channel_index="2",component_name="75.TwoHundredGigE1/0/4:1",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_channel_info_info{channel="1/0/4:1",channel_index="1",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/4"} 1.0
+transceiver_channel_info_info{channel="1/0/2:2",channel_index="2",component_name="76.TwoHundredGigE1/0/4:2",device="h3c-live-1",port="1/0/2"} 1.0
+transceiver_channel_info_info{channel="1/0/65:1",channel_index="1",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 1.0
+transceiver_channel_info_info{channel="1/0/4:2",channel_index="2",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/4"} 1.0
+transceiver_channel_info_info{channel="1/0/65:3",channel_index="3",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 1.0
+transceiver_channel_info_info{channel="1/0/65:4",channel_index="4",component_name="319.FourHundredGigE1/0/65",device="h3c-live-1",port="1/0/65"} 1.0
+transceiver_channel_info_info{channel="1/0/66:1",channel_index="1",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/66"} 1.0
+transceiver_channel_info_info{channel="1/0/4:2",channel_index="2",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/4"} 1.0
+transceiver_channel_info_info{channel="1/0/65:3",channel_index="3",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 1.0
+transceiver_channel_info_info{channel="1/0/65:4",channel_index="4",component_name="323.FourHundredGigE1/0/66",device="h3c-live-1",port="1/0/65"} 1.0
+transceiver_channel_info_info{channel="1/0/67:1",channel_index="1",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 1.0
+transceiver_channel_info_info{channel="1/0/67:2",channel_index="2",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 1.0
+transceiver_channel_info_info{channel="1/0/65:3",channel_index="3",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/65"} 1.0
+transceiver_channel_info_info{channel="1/0/67:4",channel_index="4",component_name="327.FourHundredGigE1/0/67",device="h3c-live-1",port="1/0/67"} 1.0
+transceiver_channel_info_info{channel="1/0/68:1",channel_index="1",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 1.0
+transceiver_channel_info_info{channel="1/0/67:2",channel_index="2",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 1.0
+transceiver_channel_info_info{channel="1/0/68:3",channel_index="3",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/68"} 1.0
+transceiver_channel_info_info{channel="1/0/67:4",channel_index="4",component_name="331.FourHundredGigE1/0/68",device="h3c-live-1",port="1/0/67"} 1.0
\ No newline at end of file
diff --git a/src/exporter/__pycache__/netconf_client.cpython-312.pyc b/src/exporter/__pycache__/netconf_client.cpython-312.pyc
index da6c850..f6e3cbf 100644
Binary files a/src/exporter/__pycache__/netconf_client.cpython-312.pyc and b/src/exporter/__pycache__/netconf_client.cpython-312.pyc differ
diff --git a/src/exporter/netconf_client.py b/src/exporter/netconf_client.py
index 1197418..e911568 100644
--- a/src/exporter/netconf_client.py
+++ b/src/exporter/netconf_client.py
@@ -160,7 +160,10 @@ def parse_netconf_response(
except ValueError:
ch_index = 0
- desc_elem = ch.find("oc-transceiver:state/oc-transceiver:description", NS)
+ # 优先从 config 读取 description,fallback 到 state
+ desc_elem = ch.find("oc-transceiver:config/oc-transceiver:description", NS)
+ if desc_elem is None or not _get_text(desc_elem):
+ desc_elem = ch.find("oc-transceiver:state/oc-transceiver:description", NS)
description = _get_text(desc_elem)
logical_port, logical_channel = parse_port_and_channel(
description, component_name, ch_index
diff --git a/tests/__pycache__/test_h3c_live_netconf.cpython-312-pytest-9.0.1.pyc b/tests/__pycache__/test_h3c_live_netconf.cpython-312-pytest-9.0.1.pyc
index c258c39..f1f3acc 100644
Binary files a/tests/__pycache__/test_h3c_live_netconf.cpython-312-pytest-9.0.1.pyc and b/tests/__pycache__/test_h3c_live_netconf.cpython-312-pytest-9.0.1.pyc differ
diff --git a/tests/__pycache__/test_netconf_parser.cpython-312-pytest-9.0.1.pyc b/tests/__pycache__/test_netconf_parser.cpython-312-pytest-9.0.1.pyc
index f1bd3cc..ca04419 100644
Binary files a/tests/__pycache__/test_netconf_parser.cpython-312-pytest-9.0.1.pyc and b/tests/__pycache__/test_netconf_parser.cpython-312-pytest-9.0.1.pyc differ
diff --git a/tests/test_h3c_live_netconf.py b/tests/test_h3c_live_netconf.py
index c588879..31dfa28 100644
--- a/tests/test_h3c_live_netconf.py
+++ b/tests/test_h3c_live_netconf.py
@@ -102,5 +102,185 @@ def test_h3c_live_transceiver_rpc_and_parse() -> None:
transceivers, channels = parse_netconf_response(xml_str, device_name=f"h3c-{H3C_HOST}")
- # 只要返回非空结果,就说明“连接 + filter + 解析”在真实设备上可以工作
+ # 只要返回非空结果,就说明"连接 + filter + 解析"在真实设备上可以工作
assert transceivers or channels, "H3C 设备未返回任何 transceiver/channel 数据"
+
+
+@pytest.mark.h3c_live
+def test_h3c_config_description_consistency() -> None:
+ """
+ 验证 H3C 设备上所有 transceiver 的 physical channel 的 config/description 是否正确。
+
+ 检查规则:
+ - component 名称格式如: "67.TwoHundredGigE1/0/2:1"
+ - 从中提取物理端口: "1/0/2"
+ - channel config/description 应该是: "1/0/2:1", "1/0/2:2", 等
+ - 即:config/description 的端口部分应与 component 名称中的端口部分一致
+
+ 目的:发现 H3C 设备上 config/description 配置错误的情况
+ """
+ if not _can_connect(H3C_HOST, H3C_PORT):
+ pytest.skip(f"H3C NETCONF {H3C_HOST}:{H3C_PORT} 不可达,跳过 live 测试")
+
+ flt = build_transceiver_filter()
+
+ with manager.connect(
+ host=H3C_HOST,
+ port=H3C_PORT,
+ username=H3C_USER,
+ password=H3C_PASSWORD,
+ hostkey_verify=False,
+ timeout=30,
+ allow_agent=False,
+ look_for_keys=False,
+ ) as m:
+ reply = m.get(filter=("subtree", flt))
+ xml_str = str(reply)
+
+ # 解析 XML 提取详细的 config 和 state 信息
+ import xml.etree.ElementTree as ET
+
+ NS = {
+ "oc-platform": "http://openconfig.net/yang/platform",
+ "oc-transceiver": "http://openconfig.net/yang/platform/transceiver",
+ }
+
+ root = ET.fromstring(xml_str)
+ components_path = ".//oc-platform:components/oc-platform:component"
+
+ inconsistencies = []
+ total_channels_checked = 0
+
+ for comp in root.findall(components_path, NS):
+ # 获取 component 名称
+ name_elem = comp.find("oc-platform:name", NS)
+ if name_elem is None or not name_elem.text:
+ continue
+ component_name = name_elem.text.strip()
+
+ # 只检查 TRANSCEIVER 类型
+ type_elem = comp.find("oc-platform:state/oc-platform:type", NS)
+ if type_elem is None or "TRANSCEIVER" not in type_elem.text:
+ continue
+
+ # 从 component 名称中提取端口
+ # 格式: "67.TwoHundredGigE1/0/2:1" -> 提取 "1/0/2"
+ expected_port = _extract_port_from_component_name(component_name)
+ if not expected_port:
+ # 无法从 component 名称中提取端口,跳过
+ continue
+
+ # 遍历所有 physical channels
+ channels_path = (
+ "oc-transceiver:transceiver/oc-transceiver:physical-channels/"
+ "oc-transceiver:channel"
+ )
+ for ch in comp.findall(channels_path, NS):
+ total_channels_checked += 1
+
+ # 获取 channel index
+ idx_elem = ch.find("oc-transceiver:index", NS)
+ channel_index = idx_elem.text if idx_elem is not None else "?"
+
+ # 获取 config/description
+ config_desc_elem = ch.find("oc-transceiver:config/oc-transceiver:description", NS)
+ config_desc = config_desc_elem.text.strip() if config_desc_elem is not None and config_desc_elem.text else None
+
+ # 获取 state/description
+ state_desc_elem = ch.find("oc-transceiver:state/oc-transceiver:description", NS)
+ state_desc = state_desc_elem.text.strip() if state_desc_elem is not None and state_desc_elem.text else None
+
+ # 检查 config/description
+ if config_desc:
+ # config/description 应该是 "端口:通道号" 格式,如 "1/0/2:1"
+ if ":" in config_desc:
+ config_port = config_desc.split(":", 1)[0]
+ if config_port != expected_port:
+ inconsistencies.append({
+ "component": component_name,
+ "channel_index": channel_index,
+ "expected_port": expected_port,
+ "config_desc": config_desc,
+ "config_port": config_port,
+ "issue": "config/description 端口部分与 component 名称不一致"
+ })
+ else:
+ # config/description 缺失
+ inconsistencies.append({
+ "component": component_name,
+ "channel_index": channel_index,
+ "expected_port": expected_port,
+ "config_desc": None,
+ "issue": "config/description 缺失"
+ })
+
+ # 同时检查 state/description(仅作信息收集,不作为测试失败条件)
+ # 但我们会记录这些差异,以便了解 state 数据质量
+ pass # state 检查移至单独的报告
+
+ # 生成报告
+ print(f"\n=== H3C Config/Description 一致性检查报告 ===")
+ print(f"检查的 channel 总数: {total_channels_checked}")
+ print(f"发现不一致的 channel 数: {len(inconsistencies)}")
+
+ if inconsistencies:
+ print("\n发现的不一致情况:")
+ for item in inconsistencies:
+ print(f" Component: {item['component']}")
+ print(f" Channel Index: {item['channel_index']}")
+ print(f" 期望端口: {item['expected_port']}")
+ print(f" 实际 config/description: {item.get('config_desc', 'N/A')}")
+ if 'config_port' in item:
+ print(f" config 中的端口: {item['config_port']}")
+ print(f" 问题: {item['issue']}")
+ print()
+
+ # 测试失败,config/description 应该是准确的
+ pytest.fail(
+ f"发现 {len(inconsistencies)} 个 channel 的 config/description 与 component 名称不一致,"
+ "详见上方输出"
+ )
+ else:
+ print("\n✅ 所有 channel 的 config/description 都与 component 名称一致")
+
+
+def _extract_port_from_component_name(component_name: str) -> str | None:
+ """
+ 从 component 名称中提取物理端口。
+
+ 例如:
+ - "67.TwoHundredGigE1/0/2:1" -> "1/0/2"
+ - "63.TwoHundredGigE1/0/1:1" -> "1/0/1"
+ - "323.FourHundredGigE1/0/66" -> "1/0/66"
+
+ 返回: 端口字符串,如 "1/0/2",若无法提取则返回 None
+ """
+ import re
+
+ # 匹配 "数字/数字/数字" 格式的端口
+ # 可能在 GigE 或 FourHundredGigE 等后面,可能带或不带 ":通道号"
+ pattern = r"(\d+/\d+/\d+)"
+ match = re.search(pattern, component_name)
+ if match:
+ return match.group(1)
+ return None
+
+
+def test_extract_port_from_component_name():
+ """测试从 component 名称中提取端口的辅助函数(不依赖真实设备)"""
+ # 测试各种可能的命名格式
+ test_cases = [
+ ("67.TwoHundredGigE1/0/2:1", "1/0/2"),
+ ("63.TwoHundredGigE1/0/1:1", "1/0/1"),
+ ("323.FourHundredGigE1/0/66", "1/0/66"),
+ ("64.TwoHundredGigE10/20/30:2", "10/20/30"),
+ ("SomePrefix99/88/77", "99/88/77"),
+ ("no_port_here", None),
+ ("", None),
+ ]
+ for component_name, expected_port in test_cases:
+ result = _extract_port_from_component_name(component_name)
+ assert result == expected_port, (
+ f"从 '{component_name}' 提取端口失败: "
+ f"期望 '{expected_port}', 实际 '{result}'"
+ )
diff --git a/tests/test_netconf_parser.py b/tests/test_netconf_parser.py
index 39680da..a58f936 100644
--- a/tests/test_netconf_parser.py
+++ b/tests/test_netconf_parser.py
@@ -172,3 +172,100 @@ def test_h3c_multi_component_same_serial():
assert txs[1].serial == "SN001"
assert txs[0].component_name != txs[1].component_name
+
+def test_prefer_config_description_over_state():
+ """
+ 测试当 config 和 state 的 description 不一致时,优先使用 config。
+ 这是为了解决 H3C 设备 state/description 有时错误的问题。
+ """
+ xml = """\
+
+
+
+
+ 67.TwoHundredGigE1/0/2:1
+ TRANSCEIVER
+
+
+
+ 1
+
+ 1/0/2:1
+
+
+ 1/0/2:1
+ 1.53
+
+
+
+ 2
+
+ 1/0/2:2
+
+
+ 1/0/1:2
+ 1.07
+
+
+
+
+
+
+
+
+"""
+ _, channels = parse_netconf_response(xml, "h3c-device")
+ assert len(channels) == 2
+
+ ch1 = next(c for c in channels if c.channel_index == 1)
+ ch2 = next(c for c in channels if c.channel_index == 2)
+
+ # 验证 channel 1: config 和 state 一致,结果应该是 1/0/2:1
+ assert ch1.logical_port == "1/0/2"
+ assert ch1.logical_channel == "1/0/2:1"
+ assert ch1.tx_power_dbm == 1.53
+
+ # 验证 channel 2: config 是 1/0/2:2,state 是错误的 1/0/1:2
+ # 应该优先使用 config 的值
+ assert ch2.logical_port == "1/0/2" # 从 config 的 1/0/2:2 解析
+ assert ch2.logical_channel == "1/0/2:2" # 使用 config 的值
+ assert ch2.rx_power_dbm == 1.07
+
+ # 验证两个 channel 的 port 一致(都是 1/0/2)
+ assert ch1.logical_port == ch2.logical_port
+
+
+def test_fallback_to_state_when_config_missing():
+ """
+ 测试当 config/description 不存在时,fallback 到 state/description。
+ """
+ xml = """\
+
+
+
+
+ test-component
+ TRANSCEIVER
+
+
+
+ 1
+
+ 1/0/5:1
+
+
+
+
+
+
+
+
+"""
+ _, channels = parse_netconf_response(xml, "device")
+ assert len(channels) == 1
+
+ ch = channels[0]
+ # 没有 config,应该 fallback 到 state
+ assert ch.logical_port == "1/0/5"
+ assert ch.logical_channel == "1/0/5:1"
+