@@ -946,6 +946,165 @@ class TigerTag {
946946 } ;
947947 }
948948
949+ // ── Write helpers ────────────────────────────────────────────────────────
950+
951+ /**
952+ * Map a snake_case raw-dict partial object to the camelCase kwargs accepted
953+ * by patch(). Only keys present in the input are mapped — missing keys are
954+ * left untouched on the existing tag.
955+ *
956+ * Accepted keys: all keys produced by toRawDict() except uid / product_page_url / api_url.
957+ *
958+ * @param {object } raw - Partial or full toRawDict()-style object.
959+ * @returns {object } - camelCase kwargs ready for patch().
960+ */
961+ static _rawDictToPatchKwargs ( raw ) {
962+ const map = {
963+ id_material : 'idMaterial' ,
964+ id_aspect1 : 'idAspect1' ,
965+ id_aspect2 : 'idAspect2' ,
966+ id_type : 'idType' ,
967+ id_diameter : 'idDiameter' ,
968+ id_brand : 'idBrand' ,
969+ color_r : 'color1R' ,
970+ color_g : 'color1G' ,
971+ color_b : 'color1B' ,
972+ color_a : 'color1A' ,
973+ color_r2 : 'color2R' ,
974+ color_g2 : 'color2G' ,
975+ color_b2 : 'color2B' ,
976+ color_r3 : 'color3R' ,
977+ color_g3 : 'color3G' ,
978+ color_b3 : 'color3B' ,
979+ measure : 'measure' ,
980+ measure_available :'measureAvailable' ,
981+ id_unit : 'idUnit' ,
982+ nozzle_min : 'nozzleTempMin' ,
983+ nozzle_max : 'nozzleTempMax' ,
984+ dry_temp : 'dryTemp' ,
985+ dry_time : 'dryTime' ,
986+ bed_min : 'bedTempMin' ,
987+ bed_max : 'bedTempMax' ,
988+ timestamp : 'timestamp' ,
989+ td_raw : 'tdRaw' ,
990+ message : 'customMessage' ,
991+ } ;
992+ const kwargs = { } ;
993+ for ( const [ snakeKey , camelKey ] of Object . entries ( map ) ) {
994+ if ( Object . prototype . hasOwnProperty . call ( raw , snakeKey ) ) {
995+ kwargs [ camelKey ] = raw [ snakeKey ] ;
996+ }
997+ }
998+ return kwargs ;
999+ }
1000+
1001+ /**
1002+ * Build a new TigerTag from a toRawDict()-style object (snake_case).
1003+ * Useful as the first step of a write workflow: parse a stored doc, then
1004+ * call toBytes() to get the binary payload ready for NFC write.
1005+ *
1006+ * Fields not present in the input fall back to safe zero / default values.
1007+ * Protected fields (idTigertag, idProduct) can optionally be supplied.
1008+ *
1009+ * @param {object } raw - toRawDict()-style object.
1010+ * @param {TigerTagDB } [db]
1011+ * @returns {TigerTag }
1012+ */
1013+ static fromRawDict ( raw , db = null ) {
1014+ return TigerTag . create ( {
1015+ productId : raw . id_product ?? MAKER_PRODUCT_ID ,
1016+ idMaterial : raw . id_material ?? 0 ,
1017+ idAspect1 : raw . id_aspect1 ?? 0 ,
1018+ idAspect2 : raw . id_aspect2 ?? 0 ,
1019+ idType : raw . id_type ?? 0 ,
1020+ idDiameter : raw . id_diameter ?? 0 ,
1021+ idBrand : raw . id_brand ?? 0 ,
1022+ color1R : raw . color_r ?? 0 ,
1023+ color1G : raw . color_g ?? 0 ,
1024+ color1B : raw . color_b ?? 0 ,
1025+ color1A : raw . color_a ?? 255 ,
1026+ color2R : raw . color_r2 ?? 0 ,
1027+ color2G : raw . color_g2 ?? 0 ,
1028+ color2B : raw . color_b2 ?? 0 ,
1029+ color3R : raw . color_r3 ?? 0 ,
1030+ color3G : raw . color_g3 ?? 0 ,
1031+ color3B : raw . color_b3 ?? 0 ,
1032+ measure : raw . measure ?? 0 ,
1033+ measureAvailable : raw . measure_available ?? raw . measure ?? 0 ,
1034+ idUnit : raw . id_unit ?? 0 ,
1035+ nozzleTempMin : raw . nozzle_min ?? 0 ,
1036+ nozzleTempMax : raw . nozzle_max ?? 0 ,
1037+ dryTemp : raw . dry_temp ?? 0 ,
1038+ dryTime : raw . dry_time ?? 0 ,
1039+ bedTempMin : raw . bed_min ?? 0 ,
1040+ bedTempMax : raw . bed_max ?? 0 ,
1041+ timestamp : raw . timestamp ?? null ,
1042+ customMessage : raw . message ?? '' ,
1043+ tdRaw : raw . td_raw ?? 0 ,
1044+ db,
1045+ } ) ;
1046+ }
1047+
1048+ /**
1049+ * Build a new TigerTag from a Firestore cloud document.
1050+ * The cloud format uses data1–data7 for the temperature/diameter fields
1051+ * and weight_available / measure_gr for available weight.
1052+ *
1053+ * @param {object } doc - Firestore document data object.
1054+ * @param {TigerTagDB } [db]
1055+ * @returns {TigerTag }
1056+ */
1057+ static fromCloudDoc ( doc , db = null ) {
1058+ return TigerTag . create ( {
1059+ productId : doc . id_product ?? MAKER_PRODUCT_ID ,
1060+ idMaterial : doc . id_material ?? 0 ,
1061+ idAspect1 : doc . id_aspect1 ?? 0 ,
1062+ idAspect2 : doc . id_aspect2 ?? 0 ,
1063+ idType : doc . id_type ?? 0 ,
1064+ idDiameter : doc . data1 ?? 0 , // data1 = id_diameter
1065+ idBrand : doc . id_brand ?? 0 ,
1066+ color1R : doc . color_r ?? 0 ,
1067+ color1G : doc . color_g ?? 0 ,
1068+ color1B : doc . color_b ?? 0 ,
1069+ color1A : doc . color_a ?? 255 ,
1070+ color2R : doc . color_r2 ?? 0 ,
1071+ color2G : doc . color_g2 ?? 0 ,
1072+ color2B : doc . color_b2 ?? 0 ,
1073+ color3R : doc . color_r3 ?? 0 ,
1074+ color3G : doc . color_g3 ?? 0 ,
1075+ color3B : doc . color_b3 ?? 0 ,
1076+ measure : doc . measure ?? 0 ,
1077+ measureAvailable : doc . weight_available ?? doc . measure_gr ?? doc . measure ?? 0 ,
1078+ idUnit : doc . id_unit ?? 0 ,
1079+ nozzleTempMin : doc . data2 ?? 0 , // data2 = nozzle_min
1080+ nozzleTempMax : doc . data3 ?? 0 , // data3 = nozzle_max
1081+ dryTemp : doc . data4 ?? 0 , // data4 = dry_temp
1082+ dryTime : doc . data5 ?? 0 , // data5 = dry_time
1083+ bedTempMin : doc . data6 ?? 0 , // data6 = bed_min
1084+ bedTempMax : doc . data7 ?? 0 , // data7 = bed_max
1085+ timestamp : doc . timestamp ?? null ,
1086+ customMessage : doc . message ?? '' ,
1087+ tdRaw : doc . TD ?? 0 , // TD = td_raw
1088+ db,
1089+ } ) ;
1090+ }
1091+
1092+ /**
1093+ * Apply a surgical patch using snake_case keys (toRawDict format).
1094+ * Only the supplied keys are changed — all other fields are preserved.
1095+ *
1096+ * Examples:
1097+ * tag.patchFromRawDict({ td_raw: 150 })
1098+ * tag.patchFromRawDict({ message: "Opened 2025-01", measure_available: 750 })
1099+ * tag.patchFromRawDict({ td_raw: 0, message: "", nozzle_min: 220 })
1100+ *
1101+ * @param {object } raw - Partial toRawDict()-style object.
1102+ * @returns {TigerTag } New patched instance (immutable).
1103+ */
1104+ patchFromRawDict ( raw ) {
1105+ return this . patch ( TigerTag . _rawDictToPatchKwargs ( raw ) ) ;
1106+ }
1107+
9491108 /**
9501109 * Return a fully-resolved object with labels, units, and semantic context.
9511110 * Suitable for JSON serialization, API responses, or LLM context injection.
0 commit comments